From e24578c2ea4f36649a9df95fd086feac45b9bd2e Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 03:43:48 +0800 Subject: [PATCH 01/43] Rename Hitag2 functions --- armsrc/hitag2.c | 18 +++++++++--------- client/src/cmdlfhitag.c | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/armsrc/hitag2.c b/armsrc/hitag2.c index a8f04e8a2..39e4647e0 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -320,7 +320,7 @@ static void hitag2_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_ // reader/writer // returns how long it took -static uint32_t hitag_reader_send_bit(int bit) { +static uint32_t hitag2_reader_send_bit(int bit) { // Binary pulse length modulation (BPLM) is used to encode the data stream // This means that a transmission of a one takes longer than that of a zero @@ -349,13 +349,13 @@ static uint32_t hitag_reader_send_bit(int bit) { // reader / writer commands // frame_len is in number of bits? -static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) { +static uint32_t hitag2_reader_send_frame(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 / 8] >> (7 - (i % 8))) & 1); + wait += hitag2_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1); } // Send EOF @@ -378,14 +378,14 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) // reader / writer commands // frame_len is in number of bits? -static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_len) { +static uint32_t hitag2_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]); + wait += hitag2_reader_send_bit(frame[i]); } // Send EOF @@ -1863,7 +1863,7 @@ void ReaderHitag(const lf_hitag_data_t *payload, bool ledcontrol) { } // Transmit the reader frame - command_duration = hitag_reader_send_frame(tx, txlen); + command_duration = hitag2_reader_send_frame(tx, txlen); response_start = command_start + command_duration; // Let the antenna and ADC values settle @@ -2214,7 +2214,7 @@ void WriterHitag(const lf_hitag_data_t *payload, bool ledcontrol) { } // Transmit the reader frame - command_duration = hitag_reader_send_frame(tx, txlen); + command_duration = hitag2_reader_send_frame(tx, txlen); // global write state variable used // tearoff occurred @@ -2434,9 +2434,9 @@ static void ht2_send(bool turn_on, uint32_t *cmd_start // Transmit the reader frame if (send_bits) { - *cmd_duration = hitag_reader_send_framebits(tx, txlen); + *cmd_duration = hitag2_reader_send_framebits(tx, txlen); } else { - *cmd_duration = hitag_reader_send_frame(tx, txlen); + *cmd_duration = hitag2_reader_send_frame(tx, txlen); } *resp_start = (*cmd_start + *cmd_duration); diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 6230cbf35..ec9e7361a 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -832,7 +832,7 @@ static bool ht2_get_uid(uint32_t *uid) { } if (resp.status != PM3_SUCCESS) { - PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting UID"); + PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag 2 UID"); return false; } From d13e7b0b645353f99dc38a8d8d50b7a292ff8ad3 Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 03:54:52 +0800 Subject: [PATCH 02/43] Refactor the concatbits function to support both MSB and LSB first src --- armsrc/hitagS.c | 42 ++++++++++++++++++++-------------------- client/src/cmdlfem410x.c | 4 ++-- common/commonutil.c | 12 ++++++++---- common/commonutil.h | 2 +- include/common.h | 13 +++++++++---- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 4c4e3d493..7a42fcd81 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -419,7 +419,7 @@ static void hts_init_clock(void) { static int check_select(const uint8_t *rx, uint32_t uid) { // global var? - concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32); + concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32, false); reader_selected_uid = BSWAP_32(reader_selected_uid); if (reader_selected_uid == uid) { @@ -1090,7 +1090,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz protocol_mode = packet->mode; uint8_t cmd = protocol_mode; - txlen = concatbits(tx, txlen, &cmd, 0, 5); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, t_wait, ledcontrol, true); if (rxlen != 32) { @@ -1105,10 +1105,10 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz // select uid txlen = 0; cmd = HITAGS_SELECT; - txlen = concatbits(tx, txlen, &cmd, 0, 5); - txlen = concatbits(tx, txlen, rx, 0, 32); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); + txlen = concatbits(tx, txlen, rx, 0, 32, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1140,8 +1140,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz } txlen = 0; - txlen = concatbits(tx, txlen, rnd, 0, 32); - txlen = concatbits(tx, txlen, auth_ks, 0, 32); + txlen = concatbits(tx, txlen, rnd, 0, 32, false); + txlen = concatbits(tx, txlen, auth_ks, 0, 32, false); DBG DbpString("Authenticating using key:"); DBG Dbhexdump(6, packet->key, false); @@ -1173,13 +1173,13 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz // send write page request txlen = 0; cmd = HITAGS_WRITE_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = 64; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1189,9 +1189,9 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz } txlen = 0; - txlen = concatbits(tx, txlen, packet->pwd, 0, 32); + txlen = concatbits(tx, txlen, packet->pwd, 0, 32, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1287,11 +1287,11 @@ void hts_read(const lf_hitag_data_t *payload, bool ledcontrol) { //send read request size_t txlen = 0; uint8_t cmd = HITAGS_READ_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = page_addr; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1396,13 +1396,13 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { txlen = 0; uint8_t cmd = HITAGS_WRITE_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = payload->page; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1430,9 +1430,9 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { // } txlen = 0; - txlen = concatbits(tx, txlen, payload->data, 0, 32); + txlen = concatbits(tx, txlen, payload->data, 0, 32, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); enable_page_tearoff = g_tearoff_enabled; @@ -1493,7 +1493,7 @@ int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) { size_t txlen = 0; uint8_t tx[HITAG_FRAME_LEN] = { 0x00 }; - txlen = concatbits(tx, txlen, &cmd, 0, 5); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_FIRST, ledcontrol, true); diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index 145392eb7..7f296336c 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -688,11 +688,11 @@ static int CmdEM410xClone(const char *Cmd) { uint8_t r_parity = 0; uint8_t nibble = id >> i & 0xF; - databits = concatbits(data, databits, &nibble, 4, 4); + databits = concatbits(data, databits, &nibble, 4, 4, false); for (size_t j = 0; j < 4; j++) { r_parity ^= nibble >> j & 1; } - databits = concatbits(data, databits, &r_parity, 7, 1); + databits = concatbits(data, databits, &r_parity, 7, 1, false); c_parity ^= nibble; } data[7] |= c_parity << 1; diff --git a/common/commonutil.c b/common/commonutil.c index d4eed608e..35a583988 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -555,9 +555,13 @@ void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len) { } // TODO: Boost performance by copying in chunks of 1, 2, or 4 bytes when feasible. -size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits) { +/** + * @brief Concatenate bits from src to dest, bitstream is stored MSB first + * which means that the dest_offset=0 is the MSB of the dest[0] + * + */ +size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb) { int i, end, step; - // overlap if ((src - dest) * 8 + src_offset - dest_offset > 0) { i = 0; @@ -571,8 +575,8 @@ size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_of for (; i != end; i += step) { // equiv of dest_bits[dest_offset + i] = src_bits[src_offset + i] - CLEAR_BIT(dest, dest_offset + i); - if (TEST_BIT(src, src_offset + i)) SET_BIT(dest, dest_offset + i); + CLEAR_BIT_MSB(dest, dest_offset + i); + if (src_lsb ? TEST_BIT_LSB(src, src_offset + i) : TEST_BIT_MSB(src, src_offset + i)) SET_BIT_MSB(dest, dest_offset + i); } return dest_offset + nbits; diff --git a/common/commonutil.h b/common/commonutil.h index 2ab4fbd79..2360f2a57 100644 --- a/common/commonutil.h +++ b/common/commonutil.h @@ -150,7 +150,7 @@ bool hexstr_to_byte_array(const char *hexstr, uint8_t *d, size_t *n); void reverse_arraybytes(uint8_t *arr, size_t len); void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len); -size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits); +size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb); int char2int(char c); int hexstr2ByteArr(const char *hexstr, unsigned char *array, size_t asize); #endif diff --git a/include/common.h b/include/common.h index b405b2ac7..086354750 100644 --- a/include/common.h +++ b/include/common.h @@ -202,10 +202,15 @@ extern bool g_tearoff_enabled; #endif // bit stream operations -#define TEST_BIT(data, i) (*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1 -#define SET_BIT(data, i) *((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8))) -#define CLEAR_BIT(data, i) *((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8))) -#define FLIP_BIT(data, i) *((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8))) +#define TEST_BIT_MSB(data, i) ((*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1) +#define SET_BIT_MSB(data, i) (*((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8)))) +#define CLEAR_BIT_MSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8)))) +#define FLIP_BIT_MSB(data, i) (*((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8)))) + +#define TEST_BIT_LSB(data, i) ((*((data) + ((i) / 8)) >> ((i) % 8)) & 1) +#define SET_BIT_LSB(data, i) (*((data) + ((i) / 8)) |= (1 << ((i) % 8))) +#define CLEAR_BIT_LSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << ((i) % 8))) +#define FLIP_BIT_LSB(data, i) (*((data) + ((i) / 8)) ^= (1 << ((i) % 8))) // time for decompressing and loading the image to the FPGA #define FPGA_LOAD_WAIT_TIME (1500) From 21ad101ff522716a6f2a171f28e0772f2b293bd8 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Sat, 15 Mar 2025 23:34:01 -0700 Subject: [PATCH 03/43] Major update to EM4x70 support: 1. Rework how communications with tag occur. a. bitstream to be sent to the tag is now fully pre-generated. b. bits sent and received are logged with start / end times. 2. Support built-in `hw dbg` for controlling verbosity of debug output The new bitstream generation and logging has exposed a surprising legacy behavior ... each of the command that sent additional data (beyond the command) were: * inserting an extra RM zero bit * force-enabling command parity is used This was not expected. However, this PR maintains the behavior of the existing code. TODO: Root-cause why the third RM bit is needed. Fix code to remove that hack. TODO: change the arm/client interface to ONLY use arrays of bytes, with well-defined content endianness, to avoid this problem. --- armsrc/em4x70.c | 1176 ++++++++++++---- armsrc/em4x70.h | 7 +- client/src/cmdlfem4x70.c | 9 +- doc/md/em4x70/arbitrary_lf_em_commands.md | 147 ++ doc/md/em4x70/lf_em4x70_trace_notes.md | 1517 +++++++++++++++++++++ include/em4x70.h | 40 +- 6 files changed, 2653 insertions(+), 243 deletions(-) create mode 100644 doc/md/em4x70/arbitrary_lf_em_commands.md create mode 100644 doc/md/em4x70/lf_em4x70_trace_notes.md diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 547ce5ce2..39f231157 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -25,6 +25,25 @@ #include "em4x70.h" #include "appmain.h" // tear +// Set debug level via client, e.g.: `hw dbg -4` +// For development, can force all the logging at compilation time by setting this to `true` +#define FORCE_ENABLE_LOGGING (false) + +// Define debug macros that efficiently avoid formatting the strings +// if the debug level is not high enough. Avoids rewriting the same +// checks like `if (g_dbglevel >= DBG_ERROR)` throughout the code, +// improving readability. On the downside, it does require double-parentheses +// because of the limitations of the C preprocessor (until C23). +// +// Example usage: +// DPRINTF_ERROR(("Error: %d", error_code)); +// DPRINTF_EXTENDED(("Bitstream: %s", bitstream_as_string)); +#define DPRINTF_ALWAYS(x) do { Dbprintf x ; } while (0); +#define DPRINTF_ERROR(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_ERROR )) { Dbprintf x ; } } while (0); +#define DPRINTF_INFO(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_INFO )) { Dbprintf x ; } } while (0); +#define DPRINTF_DEBUG(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_DEBUG )) { Dbprintf x ; } } while (0); +#define DPRINTF_EXTENDED(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_EXTENDED)) { Dbprintf x ; } } while (0); +#define DPRINTF_PROLIX(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel > DBG_EXTENDED)) { Dbprintf x ; } } while (0); static em4x70_tag_t tag = { 0 }; // EM4170 requires a parity bit on commands, other variants do not. @@ -56,10 +75,11 @@ static bool command_parity = true; #define EM4X70_T_READ_HEADER_LEN 16 // Read header length (16 bit periods) #define EM4X70_COMMAND_RETRIES 5 // Attempts to send/read command -#define EM4X70_MAX_RECEIVE_LENGTH 96 // Maximum bits to expect from any command +#define EM4X70_MAX_SEND_BITCOUNT 96u // Authentication == CMD(4) + NONCE(56) + DIVERGENCY(7) + FRND(28) == 6 + 56 + 35 == 56 + 41 == 95 bits (NOTE: RM(2) is handled as part of LIW detection) +#define EM4X70_MAX_RECEIVE_BITCOUNT 64u // Maximum bits to receive in response to any command (NOTE: This is EXCLUDING the 16-bit header of 0b1111'1111'1111'0000) #endif // Calculation of ticks for timing functions -#if 1 // EM4x70 Command IDs +#if 1 // EM4x70 Command IDs and notes /** * These IDs are from the EM4170 datasheet. * Some versions of the chip require a @@ -67,12 +87,114 @@ static bool command_parity = true; * The command is thus stored only in the * three least significant bits (mask 0x07). */ -#define EM4X70_COMMAND_ID 0x01 -#define EM4X70_COMMAND_UM1 0x02 -#define EM4X70_COMMAND_AUTH 0x03 -#define EM4X70_COMMAND_PIN 0x04 -#define EM4X70_COMMAND_WRITE 0x05 -#define EM4X70_COMMAND_UM2 0x07 +// // w/o parity with parity +#define EM4X70_COMMAND_ID 0x01 // 0b0001 --> 0b001'1 +#define EM4X70_COMMAND_UM1 0x02 // 0b0010 --> 0b010'1 +#define EM4X70_COMMAND_AUTH 0x03 // 0b0011 --> 0b011'0 +#define EM4X70_COMMAND_PIN 0x04 // 0b0100 --> 0b100'1 +#define EM4X70_COMMAND_WRITE 0x05 // 0b0101 --> 0b101'0 +#define EM4X70_COMMAND_UM2 0x07 // 0b0111 --> 0b111'1 + +// Command behaviors and bit counts for each direction: +// +// The command IDs and behaviors are the same for both EM4170 and V4070/EM4070, +// However, V4070/EM4070 does not support sending a PIN, reading UM2, and WRITE +// is limited to block 0..9 (other blocks don't exist). +// NOTE: It's possible that original V4070/EM4070 tags may have been manufactured +// with all ten blocks being OTP (one-time-programmable)? +// +// There are only 6 commands in total. +// Each of the six commands has two variants (i.e., with and w/o command parity). +// +// Four of the commands send a predetermined bitstream, immediately synchronize +// on the tag sending the header, and then receive a number of bits from the tag: +// +// #define EM4X70_COMMAND_ID 0x01 // 0b0001 --> 0b001'1 +// Tag: [LIW] [Header][ID₃₁..ID₀][LIW] +// Reader: [RM][Command] +// Bits Sent: RM + 4 bits +// Bits Recv: Header + 32 bits +// +// #define EM4X70_COMMAND_UM1 0x02 // 0b0010 --> 0b010'1 +// Tag: [LIW] [Header][LB₁, LB₀, UM1₂₉..UM1₀][LIW] +// Reader: [RM][Command] +// Bits Sent: RM + 4 bits +// Bits Recv: Header + 32 bits +// +// #define EM4X70_COMMAND_UM2 0x07 // 0b0111 --> 0b111'1 +// Tag: [LIW] [Header][UM2₆₃..UM2₀][LIW] +// Reader: [RM][Command] +// Bits Sent: RM + 4 bits +// Bits Recv: Header + 64 bits +// +// #define EM4X70_COMMAND_AUTH 0x03 // 0b0011 --> 0b011'0 +// Tag: [LIW] [Header][g(RN)₁₉..RN₀][LIW] +// Reader: [RM][Command][N₅₅..N₀][0000000][f(RN)₂₇..f(RN)₀] +// Bits Sent: RM + 95 bits +// Bits Recv: Header + 20 bits +// +// The SEND_PIN command requires the tag ID to be retrieved first, +// then can sends a predetermined bitstream. Unlike the above, there +// is then a wait time before the tag sends a first ACK. Then a second +// wait time before synchronizing on the tag sending the header, and +// receive a number of bits from the tag: +// +// #define EM4X70_COMMAND_PIN 0x04 // 0b0100 --> 0b100'1 +// Tag: [LIW] .. [ACK] .. [Header][ID₃₁..ID₀][LIW] +// Reader: [RM][Command][ID₃₁..ID₀][Pin₃₁..Pin₀] .. .. +// Bits Sent: RM + 68 bits +// Bits Recv: Header + 32 bits +// +// The WRITE command, given an address to write (A) and 16 bits of data (D), +// sends a predetermined bitstream. Unlike the four basic commands, there +// is then a wait time before the tag sends a first ACK, and then a second +// wait time before the tag sends a second ACK. No data is received from +// the tag ... just the two ACKs. +// +// #define EM4X70_COMMAND_WRITE 0x05 // 0b0101 --> 0b101'0 +// Tag: [LIW] .. [ACK] .. [ACK][LIW] +// Reader: [RM][Command][A₃..A₀,Ap][Data5x5] .. .. +// Bits Sent: RM + 34 bits +// Bits Recv: !!!!!!!! NONE !!!!!!!! +// +// Thus, only need to define three sequences of interaction with the tag. +// Moreover, the reader can pre-generate its entire bitstream before any bits are sent. + +// Validation of newly-written data depends on the block(s) written: +// * UM1 -- Read UM1 from the tag +// * ID -- Read ID from the tag +// * UM2 -- Read UM2 from the tag +// * KEY -- attempt authentication with the new key +// * PIN -- unlock the tag using the new PIN +// TODO: Determine if sending PIN will report success, even if the tag is already unlocked? + +// Auto-detect tag variant and command parity? +// EM4070/V4070 does not contain UM2 or PIN, and UM1 may be OTP (one-time programmable) +// EM4170 added Pin and UM2, and UM1 +// +// Thus, to check for overlap, need only check the first three commands with parity: +// | CMD | P? | Bits | Safe? | Overlaps With | Notes +// |-------|-----|----------|-------|------------------|------------ +// | ID | No | `0b0001` | Yes | None! | Safe ... indicates no parity if successful +// | UM1 | No | `0b0010` | Yes | None! | Safe ... indicates no parity if successful +// | AUTH | No | `0b0011` | Yes | ID w/parity | cannot test for no-parity, but safe to try ID w/parity +// | WRITE | No | `0b0101` | NO | | DO NOT USE ... just in case +// | PIN | No | `0b0100` | N/A | | DO NOT USE ... just in case +// | UM2 | No | `0b0111` | Yes | None! | Safe ... indicates no parity AND EM4170 tag type +// | ID | Yes | `0b0011` | Yes | Auth w/o Parity | Safe to try ... indicates parity if successful +// | UM1 | Yes | `0b0101` | Yes | Write w/o Parity | +// | AUTH | Yes | `0b0110` | Yes | None! | Not testable +// | WRITE | Yes | `0b1010` | NO | None! | DO NOT USE ... just in case +// | PIN | Yes | `0b1001` | N/A | None! | DO NOT USE ... just in case +// | UM2 | Yes | `0b1111` | Yes | None! | Safe ... indicates parity AND EM4170 tag type +// +// Thus, the following sequence of commands should auto-detect both the type of tag, +// as well as whether it requires command parity or not: +// 1. If UM2 w/o parity -- If successful, command parity is NOT required, Type is EM4170 +// 2. Elif UM2 with parity -- If successful, command parity IS required, Type is EM4170 +// 3. Elif ID w/o parity -- If successful, command parity is NOT required, Type is EM4070/V4070 +// 4. Elif ID with parity -- If successful, command parity IS required, Type is EM4070/V4070 +// 5. Else -- Error ... no tag or other error? #endif // EM4x70 Command IDs // Constants used to determine high/low state of signal @@ -220,10 +342,106 @@ static bool check_pulse_length(uint32_t pulse_tick_length, uint32_t target_tick_ (pulse_tick_length <= (target_tick_length + EM4X70_T_TAG_TOLERANCE))); } +#if 1 // brute force logging of sent buffer + +// e.g., authenticate sends 93 bits (2x RM, 56x rnd, 7x div, 28x frnd) == 2+56+35 = 58+35 = 93 +// NOTE: unlike the bitstream functions, the logs include sending of the two `RM` bits +#define EM4X70_MAX_LOG_BITS MAX(2u + EM4X70_MAX_SEND_BITCOUNT, 16u + EM4X70_MAX_RECEIVE_BITCOUNT) + +typedef struct _em4x70_log_t { + uint32_t start_tick; + uint32_t end_tick; + uint32_t bits_used; + uint8_t bit[EM4X70_MAX_LOG_BITS]; // one bit per byte +} em4x70_sublog_t; +typedef struct _em4x70_transmit_log_t { + em4x70_sublog_t transmit; + em4x70_sublog_t receive; +} em4x70_transmitted_data_log_t; +em4x70_transmitted_data_log_t g_not_used_directly; // change to bigbuff allocation? +em4x70_transmitted_data_log_t* g_Log = &g_not_used_directly; +static void log_reset(void) { + if (g_Log != NULL) { + memset(g_Log, 0, sizeof(em4x70_transmitted_data_log_t)); + } +} +static void log_dump_helper(em4x70_sublog_t * part, bool is_transmit) { + if (g_dbglevel >= DBG_INFO || FORCE_ENABLE_LOGGING) { + char const * const direction = is_transmit ? "sent >>>" : "recv <<<"; + if (part->bits_used == 0) { + DPRINTF_EXTENDED(("%s: no data", direction)); + } else { + char bitstring[EM4X70_MAX_LOG_BITS + 1]; + memset(bitstring, 0, sizeof(bitstring)); + for (int i = 0; i < part->bits_used; i++) { + bitstring[i] = part->bit[i] ? '1' : '0'; + } + DPRINTF_EXTENDED(( + "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s", + direction, + part->start_tick, part->end_tick, + part->end_tick - part->start_tick, + part->bits_used, bitstring + )); + } + } +} +static void log_dump(void) { + if (g_dbglevel >= DBG_INFO || FORCE_ENABLE_LOGGING) { + bool hasContent = false; + if (g_Log != NULL) { + uint8_t * check_for_data = (uint8_t *)g_Log; + for (size_t i = 0; i < sizeof(em4x70_transmitted_data_log_t); ++i) { + if (check_for_data[i] != 0) { + hasContent = true; + break; + } + } + } + if (hasContent) { + log_dump_helper(&g_Log->transmit, true); + log_dump_helper(&g_Log->receive, false); + } + } +} +static void log_sent_bit(uint32_t start_tick, bool bit) { + if (g_Log != NULL) { + if (g_Log->transmit.bits_used == 0) { + g_Log->transmit.start_tick = start_tick; + } + g_Log->transmit.bit[g_Log->transmit.bits_used] = bit; + g_Log->transmit.bits_used++; + } +} +static void log_sent_bit_end(uint32_t end_tick) { + if (g_Log != NULL) { + g_Log->transmit.end_tick = end_tick; + } +} +static void log_received_bit_start(uint32_t start_tick) { + if (g_Log != NULL && g_Log->receive.start_tick == 0) { + g_Log->receive.start_tick = start_tick; + } +} +static void log_received_bit_end(uint32_t end_tick) { + if (g_Log != NULL) { + g_Log->receive.end_tick = end_tick; + } +} +static void log_received_bits(uint8_t *byte_per_bit_array, size_t array_element_count) { + if (g_Log != NULL) { + memcpy(&g_Log->receive.bit[g_Log->receive.bits_used], byte_per_bit_array, array_element_count); + g_Log->receive.bits_used += array_element_count; + } +} +#endif // brute force logging of sent buffer + +// This is the only function that actually toggles modulation for sending bits static void em4x70_send_bit(bool bit) { // send single bit according to EM4170 application note and datasheet uint32_t start_ticks = GetTicks(); + log_sent_bit(start_ticks, bit); if (bit == 0) { @@ -246,70 +464,18 @@ static void em4x70_send_bit(bool bit) { LOW(GPIO_SSC_DOUT); while (TICKS_ELAPSED(start_ticks) <= EM4X70_T_TAG_FULL_PERIOD); } + log_sent_bit_end(GetTicks()); } -/** - * em4x70_send_nibble - * - * sends 4 bits of data + 1 bit of parity (with_parity) - * - */ -static void em4x70_send_nibble(uint8_t nibble, bool with_parity) { - int parity = 0; - int msb_bit = 0; - - // Non automotive EM4x70 based tags are 3 bits + 1 parity. - // So drop the MSB and send a parity bit instead after the command - if (command_parity) - msb_bit = 1; - - for (int i = msb_bit; i < 4; i++) { - int bit = (nibble >> (3 - i)) & 1; - em4x70_send_bit(bit); - parity ^= bit; - } - - if (with_parity) - em4x70_send_bit(parity); -} - -static void em4x70_send_byte(uint8_t byte) { - // Send byte msb first - for (int i = 0; i < 8; i++) - em4x70_send_bit((byte >> (7 - i)) & 1); -} - -static void em4x70_send_word(const uint16_t word) { - - // Split into nibbles - uint8_t nibbles[4]; - uint8_t j = 0; - for (int i = 0; i < 2; i++) { - uint8_t byte = (word >> (8 * i)) & 0xff; - nibbles[j++] = (byte >> 4) & 0xf; - nibbles[j++] = byte & 0xf; - } - - // send 16 bit word with parity bits according to EM4x70 datasheet - // sent as 4 x nibbles (4 bits + parity) - for (int i = 0; i < 4; i++) { - em4x70_send_nibble(nibbles[i], true); - } - - // send column parities (4 bit) - em4x70_send_nibble(nibbles[0] ^ nibbles[1] ^ nibbles[2] ^ nibbles[3], false); - - // send final stop bit (always "0") - em4x70_send_bit(0); -} - +// TODO: Add similar function that will wait for an ACK/NAK up to a given timeout. +// This will allow for more flexibile handling of tag timing in the response. static bool check_ack(void) { // returns true if signal structue corresponds to ACK, anything else is // counted as NAK (-> false) // ACK 64 + 64 // NAK 64 + 48 if (check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) { + check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) { // ACK return true; } @@ -318,49 +484,635 @@ static bool check_ack(void) { return false; } -// TODO: define and use structs for rnd, frnd, response -static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *response) { +#if 1 // #pragma region // Bitstream structures / enumerations +#define EM4X70_MAX_BITSTREAM_BITS MAX(EM4X70_MAX_SEND_BITCOUNT, EM4X70_MAX_RECEIVE_BITCOUNT) - if (find_listen_window(true)) { +// _Static_assert(EM4X70_MAX_SEND_BITCOUNT <= 255, "EM4X70_MAX_SEND_BITCOUNT must fit in uint8_t"); +// _Static_assert(EM4X70_MAX_RECEIVE_BITCOUNT <= 255, "EM4X70_MAX_RECEIVE_BITCOUNT must fit in uint8_t"); - em4x70_send_nibble(EM4X70_COMMAND_AUTH, true); +typedef struct _em4x70_bitstream_t { + // For sending, this is the number of bits to send + // For receiving, this is the number of bits expected from tag + uint8_t bitcount; + // each bit is stored as a uint8_t, storing a single bit as 0 or 1 + // this avoids bit-shifting in potentially timing-sensitive code, + // and ensures the simplest possible code for sending and receiving. + uint8_t one_bit_per_byte[EM4X70_MAX_BITSTREAM_BITS]; +} em4x70_bitstream_t; +typedef struct _em4x70_command_bitstream { + uint8_t command; // three-bit value that is encoded as the command ... used to select function to handle sending/receiving data + em4x70_bitstream_t to_send; + em4x70_bitstream_t to_receive; + // Note: Bits are stored in reverse order from transmission + // As a result, the first bit from one_bit_per_byte[0] + // ends up as the least significant bit of the LAST + // byte written. E.g., if receiving 20 bit g(rn), + // converted_to_bytes[0] will have bits: GRN03..GRN00 0 0 0 0 + // converted_to_bytes[1] will have bits: GRN11..GRN04 + // converted_to_bytes[2] will have bits: GRN19..GRN12 + // Which when treated as a 24-bit value stored little-endian, is: + // g(rn) << 8u + // This is based on how the existing code worked. + uint8_t received_data_converted_to_bytes[(EM4X70_MAX_BITSTREAM_BITS / 8) + (EM4X70_MAX_BITSTREAM_BITS % 8 ? 1 : 0)]; +} em4x70_command_bitstream_t; - // Send 56-bit Random number - for (int i = 0; i < 7; i++) { - em4x70_send_byte(rnd[i]); +typedef bool (*bitstream_command_generator_id_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_um1_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_um2_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_auth_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t * rnd, const uint8_t * frnd); +typedef bool (*bitstream_command_generator_pin_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t * tag_id, const uint32_t pin_little_endian); +typedef bool (*bitstream_command_generator_write_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, uint16_t data_little_endian, uint8_t address); + +typedef struct _em4x70_command_generators_t { + bitstream_command_generator_id_t id; + bitstream_command_generator_um1_t um1; + bitstream_command_generator_um2_t um2; + bitstream_command_generator_auth_t auth; + bitstream_command_generator_pin_t pin; + bitstream_command_generator_write_t write; +} em4x70_command_generators_t; + +#endif // #pragma endregion // Bitstream structures / enumerations +#if 1 // #pragma region // Functions to dump bitstreams to debug output +static void bitstream_dump_helper(const em4x70_bitstream_t * bitstream, bool is_transmit) { + // mimic the log's output format to make comparisons easier + char const * const direction = is_transmit ? "sent >>>" : "recv <<<"; + if (bitstream->bitcount == 0) { + if (g_dbglevel >= DBG_INFO || true) { + DPRINTF_EXTENDED(("%s: no data", direction)); + } + } else if (bitstream->bitcount > 0xFEu) { + DPRINTF_ERROR(("INTERNAL ERROR: Too many bits to dump: %d", bitstream->bitcount)); + } else { + char bitstring[EM4X70_MAX_BITSTREAM_BITS + 1]; + memset(bitstring, 0, sizeof(bitstring)); + for (uint16_t i = 0; i < bitstream->bitcount; ++i) { + bitstring[i] = bitstream->one_bit_per_byte[i] ? '1' : '0'; + } + DPRINTF_EXTENDED(( + "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s%s", + direction, + 0, 0, 0, + bitstream->bitcount + (is_transmit ? 2u : 0u), // add the two RM bits to transmitted data + is_transmit ? "00" : "", // add the two RM bits to transmitted data + bitstring + )); + } +} +static void bitstream_dump(const em4x70_command_bitstream_t * cmd_bitstream) { + bitstream_dump_helper(&cmd_bitstream->to_send, true ); + bitstream_dump_helper(&cmd_bitstream->to_receive, false); +} +#endif // #pragma region // Functions to dump bitstreams to debug output +#if 1 // #pragma region // Functions to send bitstreams, with options to receive data + +/// @brief Internal function to send a bitstream to the tag. +/// @details This function presumes a validated structure, and sends the bitstream without delays, to support timing-sensitive operations. +/// @param send The details on the bitstream to send to the tag. +/// @return +static bool send_bitstream_internal(const em4x70_bitstream_t * send) { + // similar to original send_command_and_read, but using provided bitstream + int retries = EM4X70_COMMAND_RETRIES; + + // TIMING SENSITIVE FUNCTION ... Minimize delays after finding the listen window + while (retries) { + const uint8_t * s = send->one_bit_per_byte; + uint8_t sent = 0; + retries--; + if (find_listen_window(true)) { // `true` will automatically send the two `RM` zero bits + // TIMING SENSITIVE SECTION + do { + em4x70_send_bit(*s); + s++; + sent++; + } while (sent < send->bitcount); + return true; + // TIMING SENSITIVE SECTION + } + } + return false; +} +/// @brief Internal function to send a bitstream to the tag, and immediately read response data. +/// @param send Bitstream to be sent to the tag +/// @param recv Buffer to store received data from the tag. +/// `recv->expected_bitcount` must be initialized to indicate expected bits to receive from the tag. +/// @return true only if the bitstream was sent and the expected count of bits were received from the tag. +static bool send_bitstream_and_read(em4x70_command_bitstream_t * command_bitstream) { + const em4x70_bitstream_t * send = &command_bitstream->to_send; + em4x70_bitstream_t * recv = &command_bitstream->to_receive; + + // Validate the parameters before proceeding + bool parameters_valid = true; + uint8_t bits_to_decode; + do { + if (command_bitstream->command == 0) { + DPRINTF_ERROR(("No command specified -- coding error?")); + parameters_valid = false; + bits_to_decode = 0; + } else if ( + (command_bitstream->command == EM4X70_COMMAND_ID) || + (command_bitstream->command == EM4X70_COMMAND_UM1) || + (command_bitstream->command == EM4X70_COMMAND_UM2) || + (command_bitstream->command == EM4X70_COMMAND_AUTH) + ) { + // These are the four commands that are supported by this function. + // Allow these to proceed. + } else { + DPRINTF_ERROR(("Unknown command: 0x%x (%d)", command_bitstream->command, command_bitstream->command)); + parameters_valid = false; + bits_to_decode = 0; } - // Send 7 x 0's (Diversity bits) - for (int i = 0; i < 7; i++) { - em4x70_send_bit(0); + if (send->bitcount == 0) { + DPRINTF_ERROR(("No bits to send -- coding error?")); + parameters_valid = false; + bits_to_decode = 0; + } else if (send->bitcount > EM4X70_MAX_SEND_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to send -- coding error? %d", send->bitcount)); + parameters_valid = false; + bits_to_decode = 0; } - - // Send 28-bit f(RN) - - // Send first 24 bits - for (int i = 0; i < 3; i++) { - em4x70_send_byte(frnd[i]); + if (recv->bitcount == 0) { + DPRINTF_ERROR(("No bits to receive -- coding error?")); + parameters_valid = false; + bits_to_decode = 0; + } else if (recv->bitcount > EM4X70_MAX_RECEIVE_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to receive -- coding error? %d", recv->bitcount)); + parameters_valid = false; + bits_to_decode = 0; + } else if (recv->bitcount % 8u != 0u) { + // AUTH command receives 20 bits. Existing code treated this "as if" tag sent 24 bits. + // Keep this behavior to minimize the changes to both ARM and client code bases. + bits_to_decode = ((recv->bitcount / 8u) + 1u) * 8u; // round up to nearest byte multiple + // _Static_assert(EM4X70_MAX_RECEIVE_BITCOUNT <= (UINT8_MAX - (UINT8_MAX % 8u)), "EM4X70_MAX_RECEIVE_BITCOUNT too large to safely round up within a uint8_t?"); + // No static assertion, so do this at runtime + if (bits_to_decode > EM4X70_MAX_RECEIVE_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to decode after adjusting to nearest byte multiple -- coding error? %d --> %d (max %d)", recv->bitcount, bits_to_decode, EM4X70_MAX_RECEIVE_BITCOUNT)); + parameters_valid = false; + } else { + DPRINTF_PROLIX(("Note: will receive %d bits, but decode as %d bits", recv->bitcount)); + } + } else { + // Valid number of bits expected, and an integral multiple of 8 bits ... so decode exactly what was received + bits_to_decode = recv->bitcount; } - - // Send last 4 bits (no parity) - em4x70_send_nibble((frnd[3] >> 4) & 0xf, false); - - // Receive header, 20-bit g(RN), LIW - uint8_t grnd[EM4X70_MAX_RECEIVE_LENGTH] = {0}; - int num = em4x70_receive(grnd, 20); - if (num < 20) { - if (g_dbglevel >= DBG_EXTENDED) Dbprintf("Auth failed"); - return PM3_ESOFT; - } - // although only received 20 bits - // ask for 24 bits converted because - // this utility function requires - // decoding in multiples of 8 bits - encoded_bit_array_to_bytes(grnd, 24, response); - return PM3_SUCCESS; + } while (0); + // early return when parameter validation fails + if (!parameters_valid) { + return false; } - return PM3_ESOFT; + + // similar to original send_command_and_read, but using provided bitstream + int bits_received = 0; + + // NOTE: reset of log does not track the time first bit is sent. That occurs + // when the first sent bit is recorded in the log. + log_reset(); + + // TIMING SENSITIVE SECTION + if (send_bitstream_internal(send)) { + bits_received = em4x70_receive(recv->one_bit_per_byte, recv->bitcount); + } + // END OF TIMING SENSITIVE SECTION + + // Convert the received bits into byte array (bits are received in reverse order ... this simplifies reasoning / debugging) + bool result = (bits_received == recv->bitcount); + + // output errors via debug prints and dump log as appropriate + encoded_bit_array_to_bytes(recv->one_bit_per_byte, bits_to_decode, command_bitstream->received_data_converted_to_bytes); + log_dump(); + bitstream_dump(command_bitstream); + if (bits_received == 0) { + DPRINTF_INFO(("No bits received -- tag may not be present?")); + } else if (bits_received < recv->bitcount) { + DPRINTF_INFO(("Invalid data received length: %d, expected %d", bits_received, recv->bitcount)); + } else if (bits_received != recv->bitcount) { + DPRINTF_INFO(("INTERNAL ERROR: Expected %d bits, received %d bits (more than maximum allowed)", recv->bitcount, bits_received)); + } + + // finally return the result of the operation + return result; +} +static bool send_bitstream_wait_ack_wait_read(em4x70_command_bitstream_t * command_bitstream) { + const em4x70_bitstream_t * send = &command_bitstream->to_send; + em4x70_bitstream_t * recv = &command_bitstream->to_receive; + + // Validate the parameters before proceeding + bool parameters_valid = true; + do { + if (command_bitstream->command == 0) { + DPRINTF_ERROR(("No command specified -- coding error?")); + parameters_valid = false; + } else if (command_bitstream->command != EM4X70_COMMAND_PIN) { + DPRINTF_ERROR(("Unexpected command (only supports PIN): 0x%x (%d)", command_bitstream->command, command_bitstream->command)); + parameters_valid = false; + } + + if (send->bitcount == 0) { + DPRINTF_ERROR(("No bits to send -- coding error?")); + parameters_valid = false; + } else if (send->bitcount > EM4X70_MAX_SEND_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to send -- coding error? %d", send->bitcount)); + parameters_valid = false; + } + if (recv->bitcount == 0) { + DPRINTF_ERROR(("No bits to receive -- coding error?")); + parameters_valid = false; + } else if (recv->bitcount > EM4X70_MAX_RECEIVE_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to receive -- coding error? %d", recv->bitcount)); + parameters_valid = false; + } else if (recv->bitcount % 8u != 0u) { + DPRINTF_ERROR(("PIN must transmit multiple of 8 bits -- coding error?", recv->bitcount)); + parameters_valid = false; + } + } while (0); + // early return when parameter validation fails + if (!parameters_valid) { + return false; + } + + log_reset(); + + int bits_received = 0; + // TIMING SENSITIVE SECTION -- only debug output on unrecoverable errors + if (send_bitstream_internal(send)) { + + // Wait TWALB (write access lock bits) + WaitTicks(EM4X70_T_TAG_TWALB); + + // <-- Receive ACK + if (check_ack()) { + + // Writes Lock Bits + WaitTicks(EM4X70_T_TAG_WEE); + + bits_received = em4x70_receive(recv->one_bit_per_byte, recv->bitcount); + if (bits_received != recv->bitcount) { + DPRINTF_INFO(("Invalid data received length: %d, expected %d", bits_received, recv->bitcount)); + } + } else { + DPRINTF_INFO(("No ACK received after sending command")); + } + } else { + DPRINTF_INFO(("Failed to send command")); + } + // END TIMING SENSITIVE SECTION + + // Convert the received bits into byte array (bits are received in reverse order ... this simplifies reasoning / debugging) + bool result = (bits_received == recv->bitcount); + + // output errors via debug prints and dump log as appropriate + encoded_bit_array_to_bytes(recv->one_bit_per_byte, bits_received, command_bitstream->received_data_converted_to_bytes); + log_dump(); + bitstream_dump(command_bitstream); + + return result; +} +static bool send_bitstream_wait_ack_wait_ack(em4x70_command_bitstream_t * command_bitstream) { + + const em4x70_bitstream_t * send = &command_bitstream->to_send; + em4x70_bitstream_t * recv = &command_bitstream->to_receive; + + // Validate the parameters before proceeding + bool parameters_valid = true; + do { + if (command_bitstream->command == 0) { + DPRINTF_ERROR(("No command specified -- coding error?")); + parameters_valid = false; + } else if (command_bitstream->command != EM4X70_COMMAND_WRITE) { + DPRINTF_ERROR(("Unexpected command (only supports WRITE): 0x%x (%d)", command_bitstream->command, command_bitstream->command)); + parameters_valid = false; + } + + if (send->bitcount == 0) { + DPRINTF_ERROR(("No bits to send -- coding error?")); + parameters_valid = false; + } else if (send->bitcount > EM4X70_MAX_SEND_BITCOUNT) { + DPRINTF_ERROR(("Too many bits to send -- coding error? %d", send->bitcount)); + parameters_valid = false; + } + if (recv->bitcount != 0) { + DPRINTF_ERROR(("Expecting to receive data (%d bits) -- coding error?", recv->bitcount)); + parameters_valid = false; + } + } while (0); + // early return when parameter validation fails + if (!parameters_valid) { + DPRINTF_ERROR(("Parameter validation failed")); + return false; + } + + bool result = false; + log_reset(); + + // TIMING SENSITIVE SECTION -- only debug output on unrecoverable errors + if (send_bitstream_internal(send)) { + // Wait TWA + WaitTicks(EM4X70_T_TAG_TWA); + // look for ACK sequence + if (check_ack()) { + // now EM4x70 needs EM4X70_T_TAG_TWEE (EEPROM write time) + // for saving data and should return with ACK + WaitTicks(EM4X70_T_TAG_WEE); + if (check_ack()) { + result = true; + } else { + DPRINTF_INFO(("No second ACK received after sending command")); + } + } else { + DPRINTF_INFO(("No ACK received after sending command")); + } + } else { + DPRINTF_INFO(("Failed to send command")); + } + // END TIMING SENSITIVE SECTION + + log_dump(); + bitstream_dump(command_bitstream); + return result; +} +#endif // #pragma region // Functions to send bitstreams, with options to receive data +#if 1 // #pragma region // Create bitstreams for each type of EM4x70 command + +static bool add_bit_to_bitstream(em4x70_bitstream_t * s, bool b) { + uint8_t i = s->bitcount; + uint8_t bits_to_add = 1u; + + if (i > EM4X70_MAX_BITSTREAM_BITS - bits_to_add) { + DPRINTF_ERROR(("Too many bits to add to bitstream: %d, %d", i, bits_to_add)); + return false; + } + + s->one_bit_per_byte[i] = b ? 1 : 0; + s->bitcount++; + return true; +} +static bool add_nibble_to_bitstream(em4x70_bitstream_t * s, uint8_t nibble, bool add_fifth_parity_bit) { + uint8_t i = s->bitcount; + uint8_t bits_to_add = add_fifth_parity_bit ? 5u : 4u; + + if (i > EM4X70_MAX_BITSTREAM_BITS - bits_to_add) { + DPRINTF_ERROR(("Too many bits to add to bitstream: %d, %d", i, bits_to_add)); + return false; + } + if ((nibble & 0xFu) != nibble) { + DPRINTF_ERROR(("Invalid nibble value: 0x%x", nibble)); + return false; + } + + // transmit the most significant bit first + s->one_bit_per_byte[i + 0] = nibble & 0x08u ? 1 : 0; + s->one_bit_per_byte[i + 1] = nibble & 0x04u ? 1 : 0; + s->one_bit_per_byte[i + 2] = nibble & 0x02u ? 1 : 0; + s->one_bit_per_byte[i + 3] = nibble & 0x01u ? 1 : 0; + + // add parity if requested + if (add_fifth_parity_bit) { + static const uint16_t parity = 0x6996u; // 0b0110'1001'1001'0110 -- value at bit index defines parity bit for that nibble value + s->one_bit_per_byte[i + 4] = (parity & (1u << nibble)) == 0 ? 0 : 1; + } + s->bitcount += bits_to_add; + return true; +} +static bool add_byte_to_bitstream(em4x70_bitstream_t * s, uint8_t b) { + uint8_t i = s->bitcount; + uint8_t bits_to_add = 8u; + + if (i > EM4X70_MAX_BITSTREAM_BITS - bits_to_add) { + DPRINTF_ERROR(("Too many bits to add to bitstream: %d, %d", i, bits_to_add)); + return false; + } + // transmit the most significant bit first + s->one_bit_per_byte[i + 0] = b & 0x80u ? 1 : 0; + s->one_bit_per_byte[i + 1] = b & 0x40u ? 1 : 0; + s->one_bit_per_byte[i + 2] = b & 0x20u ? 1 : 0; + s->one_bit_per_byte[i + 3] = b & 0x10u ? 1 : 0; + s->one_bit_per_byte[i + 4] = b & 0x08u ? 1 : 0; + s->one_bit_per_byte[i + 5] = b & 0x04u ? 1 : 0; + s->one_bit_per_byte[i + 6] = b & 0x02u ? 1 : 0; + s->one_bit_per_byte[i + 7] = b & 0x01u ? 1 : 0; + s->bitcount += bits_to_add; + return true; +} + + +static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { + const uint8_t expected_bits_to_send = 4u; + bool result = true; + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + out_cmd_bitstream->command = EM4X70_COMMAND_ID; + uint8_t cmd = with_command_parity ? 0x3u : 0x1u; + result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); + out_cmd_bitstream->to_receive.bitcount = 32; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + return result; +} +static bool create_legacy_em4x70_bitstream_for_cmd_um1(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { + const uint8_t expected_bits_to_send = 4u; + bool result = true; + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + out_cmd_bitstream->command = EM4X70_COMMAND_UM1; + uint8_t cmd = with_command_parity ? 0x5u : 0x2u; + result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); + out_cmd_bitstream->to_receive.bitcount = 32; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + return result; +} +static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { + const uint8_t expected_bits_to_send = 4u; + bool result = true; + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + out_cmd_bitstream->command = EM4X70_COMMAND_UM2; + uint8_t cmd = with_command_parity ? 0xFu : 0x7u; + result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); + out_cmd_bitstream->to_receive.bitcount = 64; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + return true; +} +static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t *rnd, const uint8_t *frnd) { + const uint8_t expected_bits_to_send = 96u; + bool result = true; + + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + out_cmd_bitstream->command = EM4X70_COMMAND_AUTH; + + em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + + // ********************************************************************************* + // HACK -- Insert an extra zero bit to match legacy behavior + // ********************************************************************************* + result = result && add_bit_to_bitstream(s, 0); + + // uint8_t cmd = with_command_parity ? 0x6u : 0x3u; + uint8_t cmd = 0x6u; // HACK - always sent with cmd parity + result = result && add_nibble_to_bitstream(s, cmd, false); + + // Reader: [RM][0][Command][N₅₅..N₀][0000000][f(RN)₂₇..f(RN)₀] + // + // ----> HACK <----- : [ 0 ] == extra bit of zero (!?) + // Command is 4 bits : [ 1 .. 4 ] <---- HACK: Always sent with command parity + // N is 56 bits : [ 5 .. 60 ] + // 7 bits of 0 : [61 .. 67 ] + // f(RN) is 28 bits : [68 .. 95 ] + // Total bits to send: 96 bits (not the 95 bits that are actually expected) + + // Fills in bits at indexes 5 .. 60 + for (uint_fast8_t i = 0; i < 7; ++i) { + result = result && add_byte_to_bitstream(s, rnd[i]); + } + + // Send seven diversity bits ... indexes 61 .. 67 + for (uint_fast8_t i = 0; i < 7; ++i) { + result = result && add_bit_to_bitstream(s, 0); + } + + // Send first 24 bit of f(RN) ... indexes 68 .. 91 + for (uint_fast8_t i = 0; i < 3; ++i) { + result = result && add_byte_to_bitstream(s, frnd[i]); + } + // and send the final 4 bits of f(RN) ... indexes 92 .. 95 + do { + uint8_t nibble = (frnd[3] >> 4u) & 0xFu; + result = result && add_nibble_to_bitstream(s, nibble, false); + } while (0); + + out_cmd_bitstream->to_receive.bitcount = 20; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + + return result; +} +static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t *tag_id, const uint32_t pin) { + const uint8_t expected_bits_to_send = 69; // normally 68 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit + bool result = true; + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + + em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + + out_cmd_bitstream->command = EM4X70_COMMAND_PIN; + + // ********************************************************************************* + // HACK -- Insert an extra zero bit to match legacy behavior + // ********************************************************************************* + result = result && add_bit_to_bitstream(s, 0); + + //uint8_t cmd = with_command_parity ? 0x9u : 0x4u; + uint8_t cmd = 0x9u; // HACK - always sent with cmd parity, with extra zero bit in RM? + result = result && add_nibble_to_bitstream(s, cmd, false); + + // Send tag's ID ... indexes 4 .. 35 + // e.g., tag_id points to &tag.data[4] ... &tag.data[7] + for (uint_fast8_t i = 0; i < 4; i++) { + uint8_t b = tag_id[3-i]; + result = result && add_byte_to_bitstream(s, b); + } + + // Send the PIN ... indexes 36 .. 67 + for (uint_fast8_t i = 0; i < 4 ; i++) { + // BUGBUG ... Non-portable ... likely depends on little-endian vs. big-endian (presumes little-endian) + uint8_t b = (pin >> (i * 8u)) & 0xFFu; + result = result && add_byte_to_bitstream(s, b); + } + + out_cmd_bitstream->to_receive.bitcount = 32; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + return result; +} +static bool create_legacy_em4x70_bitstream_for_cmd_write(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, uint16_t new_data, uint8_t address) { + const uint8_t expected_bits_to_send = 35u; // normally 34 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit + bool result = true; + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); + out_cmd_bitstream->command = EM4X70_COMMAND_WRITE; + + em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + + // ********************************************************************************* + // HACK -- Insert an extra zero bit to match legacy behavior + // ********************************************************************************* + result = result && add_bit_to_bitstream(s, 0); + + //uint8_t cmd = with_command_parity ? 0xAu : 0x5u; + uint8_t cmd = 0xAu; // HACK - always sent with cmd parity, with extra zero bit in RM? + result = result && add_nibble_to_bitstream(s, cmd, false); + + if ((address & 0x0Fu) != address) { + // only lower 4 bits are valid for address + DPRINTF_ERROR(("Invalid address value: 0x%x", address)); + result = false; + } + // Send address data with its even parity bit ... indexes 4 .. 8 + result = result && add_nibble_to_bitstream(s, address, true); + + // Split into nibbles ... Being explicit here because + // the client sent a uint16_t, but the order of the bytes + // is reversed relative to what is going to be sent. + // Thus, must swap the bytes before splitting into nibbles. + // TODO: Fix client and arm code to only use byte arrays..... + uint8_t nibbles[4] = { + (new_data >> 4) & 0xFu, + (new_data >> 0) & 0xFu, + (new_data >> 12) & 0xFu, + (new_data >> 8) & 0xFu, + }; + + // Send each of the four nibbles of data with their respective parity ... indexes 9 .. 28 + uint8_t column_parity = 0; + for (uint_fast8_t i = 0; i < 4; ++i) { + uint8_t nibble = nibbles[i]; + column_parity ^= nibble; + result = result && add_nibble_to_bitstream(s, nibble, true); + } + + // add the column parity ... indexes 29 .. 32 ... but manually add zero as fifth bit (it's not a parity) + result = result && add_nibble_to_bitstream(s, column_parity, false); + result = result && add_bit_to_bitstream(s, 0); + + out_cmd_bitstream->to_receive.bitcount = 0; + if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { + DPRINTF_ERROR(("INTERNAL ERROR: Expected %d bits to be added to send buffer, but only %d bits were added", expected_bits_to_send, out_cmd_bitstream->to_send.bitcount)); + result = false; + } + return result; +} +const em4x70_command_generators_t legacy_em4x70_command_generators = { + .id = create_legacy_em4x70_bitstream_for_cmd_id, + .um1 = create_legacy_em4x70_bitstream_for_cmd_um1, + .um2 = create_legacy_em4x70_bitstream_for_cmd_um2, + .auth = create_legacy_em4x70_bitstream_for_cmd_auth, + .pin = create_legacy_em4x70_bitstream_for_cmd_pin, + .write = create_legacy_em4x70_bitstream_for_cmd_write +}; +#endif // #pragma endregion // Create bitstreams for each type of EM4x70 command + +// TODO: define and use structs for rnd, frnd, response +// Or, just use the structs defined by IDLIB48? +// log entry/exit point +static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *response) { + em4x70_command_bitstream_t auth_cmd; + + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->auth(&auth_cmd, command_parity, rnd, frnd); + + bool result = send_bitstream_and_read(&auth_cmd); + if (result) { + encoded_bit_array_to_bytes(auth_cmd.to_receive.one_bit_per_byte, 24, response); + } + return result ? PM3_SUCCESS : PM3_ESOFT; } // Sets one (reflected) byte and returns carry bit @@ -412,26 +1164,25 @@ static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t * break; default: - Dbprintf("Bad block number given: %d", address); + DPRINTF_ERROR(("Bad block number given: %d", address)); return PM3_ESOFT; } // Report progress every 256 attempts if ((k % 0x100) == 0) { - Dbprintf("Trying: %04X", k); + DPRINTF_ALWAYS(("Trying: %04X", k)); } // Due to performance reason, we only try it once. Therefore you need a very stable RFID communcation. if (authenticate(temp_rnd, frnd, auth_resp) == PM3_SUCCESS) { - if (g_dbglevel >= DBG_INFO) - Dbprintf("Authentication success with rnd: %02X%02X%02X%02X%02X%02X%02X", temp_rnd[0], temp_rnd[1], temp_rnd[2], temp_rnd[3], temp_rnd[4], temp_rnd[5], temp_rnd[6]); + DPRINTF_INFO(("Authentication success with rnd: %02X%02X%02X%02X%02X%02X%02X", temp_rnd[0], temp_rnd[1], temp_rnd[2], temp_rnd[3], temp_rnd[4], temp_rnd[5], temp_rnd[6])); response[0] = (k >> 8) & 0xFF; response[1] = k & 0xFF; return PM3_SUCCESS; } if (BUTTON_PRESS() || data_available()) { - Dbprintf("EM4x70 Bruteforce Interrupted"); + DPRINTF_ALWAYS(("EM4x70 Bruteforce Interrupted at key %04X", k)); return PM3_EOPABORTED; } } @@ -439,77 +1190,28 @@ static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t * return PM3_ESOFT; } +// log entry/exit point static int send_pin(const uint32_t pin) { + em4x70_command_bitstream_t send_pin_cmd; + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->pin(&send_pin_cmd, command_parity, &tag.data[4], pin); - // sends pin code for unlocking - if (find_listen_window(true)) { - - // send PIN command - em4x70_send_nibble(EM4X70_COMMAND_PIN, true); - - // --> Send TAG ID (bytes 4-7) - for (int i = 0; i < 4; i++) { - em4x70_send_byte(tag.data[7 - i]); - } - - // --> Send PIN - for (int i = 0; i < 4 ; i++) { - em4x70_send_byte((pin >> (i * 8)) & 0xff); - } - - // Wait TWALB (write access lock bits) - WaitTicks(EM4X70_T_TAG_TWALB); - - // <-- Receive ACK - if (check_ack()) { - - // Writes Lock Bits - WaitTicks(EM4X70_T_TAG_WEE); - // <-- Receive header + ID - uint8_t tag_id[EM4X70_MAX_RECEIVE_LENGTH]; - int count_of_bits_received = em4x70_receive(tag_id, 32); - if (count_of_bits_received < 32) { - Dbprintf("Invalid ID Received"); - return PM3_ESOFT; - } - encoded_bit_array_to_bytes(tag_id, count_of_bits_received, &tag.data[4]); - return PM3_SUCCESS; - } - } - - return PM3_ESOFT; + bool result = send_bitstream_wait_ack_wait_read(&send_pin_cmd); + return result ? PM3_SUCCESS : PM3_ESOFT; } +// log entry/exit point static int write(const uint16_t word, const uint8_t address) { + em4x70_command_bitstream_t write_cmd; - // writes to specified
- if (find_listen_window(true)) { + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->write(&write_cmd, command_parity, word, address); - // send write command - em4x70_send_nibble(EM4X70_COMMAND_WRITE, true); - - // send address data with parity bit - em4x70_send_nibble(address, true); - - // send data word - em4x70_send_word(word); - - // Wait TWA - WaitTicks(EM4X70_T_TAG_TWA); - - // look for ACK sequence - if (check_ack()) { - - // now EM4x70 needs EM4X70_T_TAG_TWEE (EEPROM write time) - // for saving data and should return with ACK - WaitTicks(EM4X70_T_TAG_WEE); - if (check_ack()) { - - return PM3_SUCCESS; - } - } + bool result = send_bitstream_wait_ack_wait_ack(&write_cmd); + if (!result) { + DPRINTF_INFO(("Failed to write data")); } - return PM3_ESOFT; + return result ? PM3_SUCCESS : PM3_ESOFT; } @@ -524,19 +1226,21 @@ static bool find_listen_window(bool command) { 96 ( 64 + 32 ) 64 ( 32 + 16 +16 )*/ - if (check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && - check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), EM4X70_T_TAG_FULL_PERIOD + (2 * EM4X70_T_TAG_HALF_PERIOD))) { + if (check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), (1 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD)) { if (command) { /* Here we are after the 64 duration edge. - * em4170 says we need to wait about 48 RF clock cycles. - * depends on the delay between tag and us - * - * I've found between 4-5 quarter periods (32-40) works best - */ - WaitTicks(4 * EM4X70_T_TAG_QUARTER_PERIOD); + * em4170 says we need to wait about 48 RF clock cycles. + * depends on the delay between tag and us + * + * I've found 32-40 field cycles works best + * Allow user adjustment in range: 24-48 field cycles? + * On PM3Easy I've seen success at 24..40 field + */ + WaitTicks(40 * TICKS_PER_FC); // Send RM Command em4x70_send_bit(0); em4x70_send_bit(0); @@ -557,7 +1261,7 @@ static bool find_listen_window(bool command) { static void encoded_bit_array_to_bytes(const uint8_t *bits, int count_of_bits, uint8_t *out) { if (count_of_bits % 8 != 0) { - Dbprintf("Should have a multiple of 8 bits, was sent %d", count_of_bits); + DPRINTF_ERROR(("Should have a multiple of 8 bits, was sent %d", count_of_bits)); } int num_bytes = count_of_bits / 8; // We should have a multiple of 8 here @@ -580,39 +1284,21 @@ static uint8_t encoded_bit_array_to_byte(const uint8_t *bits, int count_of_bits) return byte; } -static bool send_command_and_read(uint8_t command, uint8_t *bytes, size_t expected_byte_count) { - - int retries = EM4X70_COMMAND_RETRIES; - while (retries) { - retries--; - - if (find_listen_window(true)) { - uint8_t bits[EM4X70_MAX_RECEIVE_LENGTH] = {0}; - size_t out_length_bits = expected_byte_count * 8; - em4x70_send_nibble(command, command_parity); - int len = em4x70_receive(bits, out_length_bits); - if (len < out_length_bits) { - Dbprintf("Invalid data received length: %d, expected %d", len, out_length_bits); - return false; - } - encoded_bit_array_to_bytes(bits, len, bytes); - return true; - } - } - return false; -} - - - /** * em4x70_read_id * * read pre-programmed ID (4 bytes) */ static bool em4x70_read_id(void) { + em4x70_command_bitstream_t read_id_cmd; + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->id(&read_id_cmd, command_parity); - return send_command_and_read(EM4X70_COMMAND_ID, &tag.data[4], 4); - + bool result = send_bitstream_and_read(&read_id_cmd); + if (result) { + encoded_bit_array_to_bytes(read_id_cmd.to_receive.one_bit_per_byte, read_id_cmd.to_receive.bitcount, &tag.data[4]); + } + return result; } /** @@ -621,9 +1307,17 @@ static bool em4x70_read_id(void) { * read user memory 1 (4 bytes including lock bits) */ static bool em4x70_read_um1(void) { + em4x70_command_bitstream_t read_um1_cmd; + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->um1(&read_um1_cmd, command_parity); - return send_command_and_read(EM4X70_COMMAND_UM1, &tag.data[0], 4); + bool result = send_bitstream_and_read(&read_um1_cmd); + if (result) { + encoded_bit_array_to_bytes(read_um1_cmd.to_receive.one_bit_per_byte, read_um1_cmd.to_receive.bitcount, &tag.data[0]); + } + bitstream_dump(&read_um1_cmd); + return result; } /** @@ -632,17 +1326,26 @@ static bool em4x70_read_um1(void) { * read user memory 2 (8 bytes) */ static bool em4x70_read_um2(void) { + em4x70_command_bitstream_t read_um2_cmd; + const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + generator->um2(&read_um2_cmd, command_parity); - return send_command_and_read(EM4X70_COMMAND_UM2, &tag.data[24], 8); + bool result = send_bitstream_and_read(&read_um2_cmd); + if (result) { + encoded_bit_array_to_bytes(read_um2_cmd.to_receive.one_bit_per_byte, read_um2_cmd.to_receive.bitcount, &tag.data[24]); + } + + bitstream_dump(&read_um2_cmd); + return result; } - static bool find_em4x70_tag(void) { // function is used to check whether a tag on the proxmark is an - // EM4170 tag or not -> speed up "lf search" process + // EM4x70 tag or not -> speed up "lf search" process return find_listen_window(false); } +// This is the ONLY function that receives data from the tag static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read) { uint32_t pl; @@ -654,7 +1357,7 @@ static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read) { // 12 Manchester 1's (may miss some during settle period) // 4 Manchester 0's - // Skip a few leading 1's as it could be noisy + // Skip about half of the leading 1's as signal could start off noisy WaitTicks(6 * EM4X70_T_TAG_FULL_PERIOD); // wait until we get the transition from 1's to 0's which is 1.5 full windows @@ -671,13 +1374,14 @@ static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read) { return 0; } - // Skip next 3 0's, header check consumes the first 0 + // Skip next 3 0's, (the header check above consumed the first 0) for (int i = 0; i < 3; i++) { // If pulse length is not 1 bit, then abort early if (!check_pulse_length(get_pulse_length(edge), EM4X70_T_TAG_FULL_PERIOD)) { return 0; } } + log_received_bit_start(GetTicks()); // identify remaining bits based on pulse lengths // between listen windows only pulse lengths of 1, 1.5 and 2 are possible @@ -727,13 +1431,17 @@ static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read) { break; } } + log_received_bit_end(GetTicks()); + log_received_bits(bits, bit_pos); return bit_pos; } +// CLIENT ENTRY POINTS void em4x70_info(const em4x70_data_t *etd, bool ledcontrol) { bool success = false; + bool success_with_UM2 = false; // Support tags with and without command parity bits command_parity = etd->parity; @@ -743,14 +1451,22 @@ void em4x70_info(const em4x70_data_t *etd, bool ledcontrol) { // Find the Tag if (get_signalproperties() && find_em4x70_tag()) { - // Read ID, UM1 and UM2 - success = em4x70_read_id() && em4x70_read_um1() && em4x70_read_um2(); + // Read ID and UM1 (both em4070 and em4170) + success = em4x70_read_id() && em4x70_read_um1(); + // em4170 also has UM2, V4070 does not (e.g., 1998 Porsche Boxster) + success_with_UM2 = em4x70_read_um2(); } StopTicks(); lf_finalize(ledcontrol); int status = success ? PM3_SUCCESS : PM3_ESOFT; - reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, sizeof(tag.data)); + size_t data_size = + success && success_with_UM2 ? 32 : + success ? 20 : + 0; + + // not returning the data to the client about actual length read? + reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, data_size); } void em4x70_write(const em4x70_data_t *etd, bool ledcontrol) { @@ -760,9 +1476,9 @@ void em4x70_write(const em4x70_data_t *etd, bool ledcontrol) { // Disable to prevent sending corrupted data to the tag. if (command_parity) { - Dbprintf("Use of `--par` option with `lf em 4x70 write` is disabled to prevent corrupting tag data"); - reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); - return; + DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 write` is non-functional and may corrupt data on the tag.")); + // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); + // return; } init_tag(); @@ -831,9 +1547,9 @@ void em4x70_auth(const em4x70_data_t *etd, bool ledcontrol) { // Disable to prevent sending corrupted data to the tag. if (command_parity) { - Dbprintf("Use of `--par` option with `lf em 4x70 auth` is disabled to prevent corrupting tag data"); - reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); - return; + DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 auth` is non-functional.")); + // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); + // return; } init_tag(); @@ -859,9 +1575,9 @@ void em4x70_brute(const em4x70_data_t *etd, bool ledcontrol) { // Disable to prevent sending corrupted data to the tag. if (command_parity) { - Dbprintf("Use of `--par` option with `lf em 4x70 brute` is disabled to prevent corrupting tag data"); - reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); - return; + DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 brute` is non-functional and may corrupt data on the tag.")); + // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); + // return; } init_tag(); @@ -887,9 +1603,9 @@ void em4x70_write_pin(const em4x70_data_t *etd, bool ledcontrol) { // Disable to prevent sending corrupted data to the tag. if (command_parity) { - Dbprintf("Use of `--par` option with `lf em 4x70 setpin` is disabled to prevent corrupting tag data"); - reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); - return; + DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 setpin` is non-functional and may corrupt data on the tag.")); + // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); + // return; } init_tag(); @@ -936,9 +1652,9 @@ void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) { // Disable to prevent sending corrupted data to the tag. if (command_parity) { - Dbprintf("Use of `--par` option with `lf em 4x70 setkey` is disabled to prevent corrupting tag data"); - reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); - return; + DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 setkey` is non-functional and may corrupt data on the tag.")); + // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); + // return; } init_tag(); @@ -964,9 +1680,9 @@ void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) { // The client now has support for test authentication after // writing a new key, thus allowing to verify that the new // key was written correctly. This is what the datasheet - // suggests. Not currently implemented in the firmware, - // although ID48LIB has no dependencies that would prevent - // use within the firmware layer. + // suggests. Not currently implemented in the firmware. + // ID48LIB has no dependencies that would prevent this from + // being implemented directly within the firmware layer... } } diff --git a/armsrc/em4x70.h b/armsrc/em4x70.h index 541e1cf51..deca58b0c 100644 --- a/armsrc/em4x70.h +++ b/armsrc/em4x70.h @@ -19,12 +19,11 @@ #ifndef EM4x70_H #define EM4x70_H +#include +#include +#include #include "../include/em4x70.h" -typedef struct { - uint8_t data[32]; -} em4x70_tag_t; - typedef enum { RISING_EDGE, FALLING_EDGE diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index cffac044d..c9b11f991 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -34,9 +34,15 @@ // TODO: Optional: use those unique structures in a union, call it em4x70_data_t, but add a first // common header field that includes the command itself (to improve debugging / validation). + typedef struct _em4x70_tag_info_t { /// - /// The full data on an em4x70 the tag. + /// The full data on an em4170 tag. + /// For V4070 tags: + /// * UM2 does not exist on the tag + /// * Pin does not exist on the tag + /// * UM1 (including the lock bits) might be one-time programmable (OTP) + /// /// [31] == Block 15 MSB == UM2₆₃..UM2₅₆ /// [30] == Block 15 LSB == UM2₅₅..UM2₄₈ /// [29] == Block 14 MSB == UM2₄₇..UM2₄₀ @@ -168,6 +174,7 @@ typedef struct _em4x70_cmd_input_calculate_t { ID48LIB_KEY key; ID48LIB_NONCE rn; } em4x70_cmd_input_calculate_t; + typedef struct _em4x70_cmd_output_calculate_t { ID48LIB_FRN frn; ID48LIB_GRN grn; diff --git a/doc/md/em4x70/arbitrary_lf_em_commands.md b/doc/md/em4x70/arbitrary_lf_em_commands.md new file mode 100644 index 000000000..40300c938 --- /dev/null +++ b/doc/md/em4x70/arbitrary_lf_em_commands.md @@ -0,0 +1,147 @@ +# arbitrary lf em commands + +Goals: +1. Improved logging of `lf em` commands and responses +2. Greater certainty in command sequences +3. Easier testing of new commands + +## Methodology + +This is documenting the actual commands used by existing code. Phases include: +* Document the existing command sequences +* Document the existing logging APIs +* Define small set of timing-sensitive functions as abstractions +* Implement the abstractions +* Add logging + +The goal is to improve logging and debugging, and allow easily testing new LF commands. + +## EM4x70 (aka ID48, aka Megamos) + +Only six command sequences currently used: + +#define EM4X70_COMMAND_ID 0x01 +#define EM4X70_COMMAND_UM1 0x02 +#define EM4X70_COMMAND_AUTH 0x03 +#define EM4X70_COMMAND_PIN 0x04 +#define EM4X70_COMMAND_WRITE 0x05 +#define EM4X70_COMMAND_UM2 0x07 + + + +### ID Command + +Wait for `LIW` (listen window), and start transmission at next `LIW`: + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window sync +reader | `0b00` | RM +reader | `0b001` | CMD +reader | `0b1` | command parity bit +tag | HEADER | HEADER (0b1111'1111'1111'0000) +tag | 32-bits | ID (D31..D0) +tag | LIW | tag reverts to be ready for next command + +### UM1 Command + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window +reader | `0b00` | RM +reader | `0b010` | CMD +reader | `0b1` | command parity bit +tag | 16-bits | HEADER +tag | 32-bits | UM1 data +tag | LIW | tag reverts to be ready for next command + +### UM2 Command + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window +reader | `0b00` | RM +reader | `0b111` | CMD +reader | `0b1` | command parity bit +tag | 16-bits | HEADER +tag | 64-bits | UM2 data +tag | LIW | tag reverts to be ready for next command + + +### Auth Command + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window +reader | `0b00` | RM +reader | `0b011` | CMD +reader | `0b0` | command parity bit +reader | 56-bits | RN +reader | 7-bits | Tdiv == 0b0000000 (always zero) +reader | 28-bits | f(RN) +tag | 16-bits | HEADER +tag | 20-bits | g(RN) +tag | LIW | tag reverts to be ready for next command + +### Write Word + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window +reader | `0b00` | RM +reader | `0b101` | CMD +reader | `0b0` | command parity bit +reader | 4-bits | address/block to write +reader | 1-bit | address/block parity bit +reader | 25-bits | 5x5 data w/ row and column parity +tag | ACK | Wait (TWA) for ACK ... time to wait before searching for ACK +tag | ACK | Wait (WEE) for ACK ... time to wait before searching for ACK +tag | LIW | tag reverts to be ready for next command + + + + +### PIN Command + +source | bits | comment +----------|---------|--------- +tag | LIW | listen window +reader | `0b00` | RM +reader | `0b100` | CMD +reader | `0b1` | command parity bit +reader | 32-bits | ID of the tag +reader | 32-bits | PIN +tag | ACK | Wait (TWALB) for ACK ... time to wait before searching for ACK +tag | HEADER | DELAYED (TWEE) header ... time to wait before searching for header +tag | 32-bits | ID of the tag +tag | LIW | tag reverts to be ready for next command + + +### Abstraction required + +Possible items to abstract: +* bits to send: quantity of bits to be sent + storage containing those bits +* bits to receive: expected bits to receive + storage to receive those bits +* LIW: special-case handling to synchronize next command +* ACK: special-case handling to wait for ACK +* HEADER: special-case handling to wait for HEADER +* DELAY: ticks to delay before processing next item + +Special handling required for: +* `HEADER` --> 12-bits of zero, 4-bits of one. Consider a timeout: if tag disappears, no pulse found, while sometimes expect long time before HEADER appears (as in SEND_PIN). Read of header may miss the first few bits during transition, so need to special-case handling of this detection. +* `LIW` --> Timing-sensitive, syncs reader with tag ... reader must send during 32 period where chip's modulator is ON. +* `ACK` --> This is currently a time-to-delay. + Should this be a maximum time to wait for ACK? + Currently, could sit waiting for long time + if no tag present, as `check_ack()` has no timeout. + +```C +WaitTicks(EM4X70_T_TAG_TWA); +if (check_ack()) + WaitTicks(EM4X70_T_TAG_WEE); + if (check_ack()) + return PM3_SUCCESS; +``` + + + diff --git a/doc/md/em4x70/lf_em4x70_trace_notes.md b/doc/md/em4x70/lf_em4x70_trace_notes.md new file mode 100644 index 000000000..cd194918b --- /dev/null +++ b/doc/md/em4x70/lf_em4x70_trace_notes.md @@ -0,0 +1,1517 @@ +# EM4x70 Logging + +Full log of all bits sent, and all bits received, when +exercising each of the `lf em 4x70` commands. + +This can be used to ensure no regressions in what is sent +(and received) when modifying the `lf em 4x70` code. + +## Discovered Potential Bugs + +* [X] Last four bits of FRN appear to always be 0b1111 (0xF) instead of intended value 0b1100 (0xC)? + FIXED -- log buffer was too small ... needs to be 98 bits due to inclusion of two RM bits +* [ ] Semi-randomized application of parity to command: + * EM4X70_COMMAND_ID -- No parity + * EM4X70_COMMAND_UM1 -- No parity + * EM4X70_COMMAND_UM2 -- No parity + * EM4X70_COMMAND_WRITE -- Always with fifth parity bit added + * EM4X70_COMMAND_AUTH -- Always with fifth parity bit added + * EM4X70_COMMAND_PIN -- Always with fifth parity bit added + +## Comparison of cmds send with / without `--par` + +This section will only list the commands sent, and break them down into their components. +It's intended to be compact, and document what the code did as of 2024-05-01 (or thereabouts). +First will be log without, followed by log with `--par` option. + +### `lf em 4x70 info` + +Bits sent to the tag are IDENTICAL. +The `--par` option is IGNORED ... would have expected +to see CMD values of `0011`, `0101`, and `1111` instead! + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits:| 00 | 0001 | | +[#] sent >>>: 6 bits:| 00 | 0010 | | +[#] sent >>>: 6 bits:| 00 | 0111 | | +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits:| 00 | 0001 | | +[#] sent >>>: 6 bits:| 00 | 0010 | | +[#] sent >>>: 6 bits:| 00 | 0111 | | +[#] REM --------------|----|--------|--------|-------------- +``` + + +### `lf em 4x70 write -b 13 -d C65B` + +When `--par` is used, the command is treated as a three-bit +command, and a parity bit calculated and added. + +As can be seen, the existing code was re-using that logic +also for the address and data bits. This might be OK as +to the address bits (unlikely, but possible). This is most +definitely wrong as to the data bits. + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1101 1 | 1100 0 0110 0 0101 0 1011 1 0100 0 +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 30 bits:| 00 | 101 0 | 101 0 | 100 1 110 0 101 0 011 0 100 0 +[#] REM --------------|----|--------|--------|-------------- +``` + + +### `lf em 4x70 setpin --pin 12345678` + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits:| 00 | 0001 | | +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1011 1 | 0001 1 0010 1 0011 0 0100 1 0100 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1010 0 | 0101 0 0110 0 0111 1 1000 1 1100 0 +[#] sent >>>: 71 bits:| 00 | 0100 1 | | 0111 1000 1011 1000 // ID nibbles 1-4 + | | | | 1110 0000 0001 0010 // ID nibbles 5-8 + | | | | 0001 0010 0011 0100 // Pin nibbles 1-4 + | | | | 0101 0110 0111 1000 // Pin nibbles 5-8 +[#] sent >>>: 6 bits:| 00 | 0010 | | +[#] sent >>>: 6 bits:| 00 | 0111 | | +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits: | 00 | 001 1 | | +[#] REM --------------|----|--------|--------|-------------- +``` + +Surprisingly, `setpin` attempts to send `EM4X70_COMMAND_ID` as a 3-bit +value (+ parity over those three bits). My tag rejects the command +at this point, as it interprets the command as `EM4X70_COMMAND_AUTH`. + +TODO: Test code path on tag that requires 3-bit commands w/ parity bit. + + +### `lf em 4x70 unlock --pin AAAAAAAA` + +TODO: Test code path on tag that requires 3-bit commands w/ parity bit. + +My current tags properly reject the earliest command (sent as 3-bit +`EM4X70_COMMAND_ID` + its parity) as being `EM4X70_COMMAND_AUTH`. +Thus, it properly rejects the command. + +Writes pin to blocks 11, 10, then unlocks using that pin code. + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits:| 00 | 0001 | | +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1011 1 | 0001 1 0010 1 0011 0 0100 1 0100 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1010 0 | 0101 0 0110 0 0111 1 1000 1 1100 0 +[#] sent >>>: 71 bits:| 00 | 0100 1 | | 0111 1000 1011 1000 + | | | | 1110 0000 0001 0010 + | | | | 0001 0010 0011 0100 + | | | | 0101 0110 0111 1000 +[#] sent >>>: 6 bits:| 00 | 0010 | | +[#] sent >>>: 6 bits:| 00 | 0111 | | +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits: | 00 | 001 1 | | +[#] REM --------------|----|--------|--------|-------------- +``` + +### `lf em 4x70 setkey -k 022A028C02BE000102030405` + +TODO: Test code path on tag that requires 3-bit commands w/ parity bit. + +My current tags properly reject the earliest command (sent as 3-bit +`EM4X70_COMMAND_ID` + its parity) as being `EM4X70_COMMAND_AUTH`. +Thus, it properly rejects the command. + + + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits:| 00 | 0001 | | +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1001 0 | 0000 0 0010 1 0010 1 1010 0 1010 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 1000 1 | 0000 0 0010 1 1000 1 1100 0 0110 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 0111 1 | 0000 0 0010 1 1011 1 1110 1 0111 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 0110 0 | 0000 0 0000 0 0000 0 0001 1 0001 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 0101 0 | 0000 0 0010 1 0000 0 0011 0 0001 0 +[#] sent >>>: 37 bits:| 00 | 0101 0 | 0100 1 | 0000 0 0100 1 0000 0 0101 0 0001 0 +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 6 bits: | 00 | 001 1 | | +[#] REM --------------|----|--------|--------|-------------- +``` + + +### `lf em 4x70 auth --rnd 7D5167003571F8 --frn 982DBCC0` + +The only problem is that the final nibble of FRN +is missing its most significant bit. + +``` +[#] REM | RM | CMD | Addr | Data.... +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 98 bits:| 00 | 0011 0 | | 0111 1101 0101 0001 \ + | 0110 0111 0000 0000 \_Rnd + | 0011 0101 0111 0001 / + | 1111 1000 / + | 0000000 - Tdiv + | 1001 1000 0010 1101 \_FRN + | 1011 1100 1100 / +[#] REM --------------|----|--------|--------|-------------- +[#] sent >>>: 96 bits:| 00 | 011 0 | | 0111 1101 0101 0001 \ + | 0110 0111 0000 0000 \_Rnd + | 0011 0101 0111 0001 / + | 1111 1000 / + | 0000000 - Tdiv + | 1001 1000 0010 1101 \_FRN + | 1011 1100 100 / +[#] REM --------------|----|--------|--------|-------------- +``` + +## More Comprehensive logs ... Without `--par` option + +
Hiding by default as not critical

+ +### lf em 4x70 info + +`[usb|script] pm3 --> lf em 4x70 info` + +#### log + +``` +[#] sent >>>: [ 17169 .. 19545 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 25999 .. 38288 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 45022 .. 47399 ] ( 2377 ) 6 bits: 000010 +[#] recv <<<: [ 53862 .. 66151 ] ( 12289 ) 32 bits: 10100001110111110101011001111000 +[#] sent >>>: [ 72886 .. 75260 ] ( 2374 ) 6 bits: 000111 +[#] recv <<<: [ 81702 .. 106281 ] ( 24579 ) 64 bits: 0001001000110100101010101010101010101010101010101010101010101010 +``` + +#### decoded log + +``` +[#] sent >>>: 6 bits: 00 0001 + 2-bit RM: 00 + 4-bit CMD: 0001 (EM4X70_COMMAND_ID) +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 + 7 8 B 8 E 0 1 2 + +[#] sent >>>: 6 bits: 00 0010 + 2-bit RM: 00 + 4-bit CMD: 0010 (EM4X70_COMMAND_UM1) +[#] recv <<<: 32 bits: 1010 0001 1101 1111 0101 0110 0111 1000 + A 1 D F 5 6 7 8 + +[#] sent >>>: 6 bits: 00 0111 + 2-bit RM: 00 + 4-bit CMD: 0111 (EM4X70_COMMAND_UM2) +[#] recv <<<: 64 bits: 0001 0010 0011 0100 1010 1010 1010 1010 1010 1010 1010 1010 1010 1010 1010 1010 + 1 2 3 4 A A A A A A A A A A A A +``` + + + + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | 12 34 | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | A1 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 1 +[=] Tag is UNLOCKED. +[=] +``` + +### lf em 4x70 write (UM2 block 15) + +`[usb|script] pm3 --> lf em 4x70 write -b 15 -d 576B` + +#### log + +``` +[#] sent >>>: [ 17163 .. 31600 ] ( 14437 ) 37 bits: 0001010111100101001111011001011111110 +[#] recv <<<: no data +[#] ... lines that retrieve ID/UM1/UM2 removed ... +``` + +#### decoded log + +``` +[#] sent >>>: 37 bits: 00 01010 11110 10100 10100 10100 10100 00000 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 1111 0 == 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1011 1 == 0xB 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0110 0 == 0x6 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0111 1 == 0x7 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0101 0 == 0x5 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 1111 0 == address to write to + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + command parity (!!!) + \\---------------------------------------------- 00 == RM +``` + + + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | 57 6B | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | A1 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 1 +[=] Tag is UNLOCKED. +[=] +``` + +### lf em 4x70 setkey (autorecovery test key) + +This writes to block 9..4. +`[usb|script] pm3 --> lf em 4x70 setkey -k 022A028C02BE000102030405` + +#### log + +``` +[#] sent >>>: [ 17163 .. 19539 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 25993 .. 38282 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 45015 .. 59453 ] ( 14438 ) 37 bits: 0001010100100000000101001011010010100 +[#] recv <<<: no data +[#] sent >>>: [ 114111 .. 128547 ] ( 14436 ) 37 bits: 0001010100010000000101100011100001100 +[#] recv <<<: no data +[#] sent >>>: [ 183207 .. 197646 ] ( 14439 ) 37 bits: 0001010011110000000101101111110101110 +[#] recv <<<: no data +[#] sent >>>: [ 252303 .. 266738 ] ( 14435 ) 37 bits: 0001010011000000000000000000001100010 +[#] recv <<<: no data +[#] sent >>>: [ 321387 .. 335820 ] ( 14433 ) 37 bits: 0001010010100000000101000000011000010 +[#] recv <<<: no data +[#] sent >>>: [ 390471 .. 404903 ] ( 14432 ) 37 bits: 0001010010010000001001000000101000010 +[#] recv <<<: no data +[#] sent >>>: [ 17157 .. 55333 ] ( 38176 ) 98 bits: 00001101110111100100011110001101111111011101100001001011000011000000001001100111011101100011111100 +[#] recv <<<: [ 61410 .. 69091 ] ( 7681 ) 20 bits: 10010001110110101000 +``` + +#### decoded log + +``` +[usb|script] pm3 --> lf em 4x70 setkey -k 022A 028C 02BE 0001 0203 0405 + + +[#] sent >>>: 6 bits: 00 0001 + RM EM4X70_COMMAND_ID +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 + 7 8 B 8 E 0 1 2 + +[#] sent >>>: 37 bits: 00 01010 10010 00000 00101 00101 10100 10100 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 1010 0 == 0xA 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1010 0 == 0xA 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0010 1 == 0x2 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0010 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 1001 0 == 0x9 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 10001 00000 00101 10001 11000 01100 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0110 0 == 0x6 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1100 0 == 0xC 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 1000 1 == 0x8 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0010 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 1000 1 == 0x8 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 01111 00000 00101 10111 11101 01110 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0111 0 == 0x7 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1110 1 == 0xE 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 1011 1 == 0xB 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0010 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 0111 1 == 0x7 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 01100 00000 00000 00000 00011 00010 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0001 0 == 0x1 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 0001 1 == 0x1 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0000 0 == 0x0 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0000 0 == 0x0 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 0110 0 == 0x6 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 01010 00000 00101 00000 00110 00010 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0001 0 == 0x1 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 0011 0 == 0x3 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0000 0 == 0x0 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0010 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 0101 0 == 0x5 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 01001 00000 01001 00000 01010 00010 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0001 0 == 0x1 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 0101 0 == 0xC 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0000 0 == 0x8 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0100 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0000 0 == 0x0 1st row nibble + row parity + || ||||| \\\\\---------------------------------- 0100 1 == 0x8 address + address parity + || \\\\\---------------------------------------- 0101 0 == EM4X70_COMMAND_WRITE + parity + \\---------------------------------------------- 00 == RM bits + + +Equivalent of: lf em 4x70 auth --rnd EF23C6FEEC2586 --frn 99DD8FC0 --> 91DA80 + +[#] sent >>>: 98 bits: 00 00110 1110 1111 0010 0011 1100 0110 1111 1110 1110 1100 0010 0101 1000 0110 0000000 1001 1001 1101 1101 1000 1111 1100 + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/---- FRN + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| 9 9 D D 8 F C + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| \\\\\\\---------------------------------------- Tdiv (always seven zero bits) + || ||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/-------------------------------------------------- 56-bits RN + || ||||| E F 2 3 C 6 F E E C 2 5 8 6 + || \\\\\---------------------------------------- 0011 0 == EM4X70_COMMAND_AUTH + parity + \\---------------------------------------------- 00 == RM bits +[#] recv <<<: 20 bits: 1001 0001 1101 1010 1000 + 9 1 D A 8 ---- GRN +``` + + +#### other output +``` +[=] Writing new key ( ok ) +[=] Verifying auth for new key: 022a028c02be000102030405 --> lf em 4x70 auth --rnd EF23C6FEEC2586 --frn 99DD8FC0 --> 91DA80 +[=] Authenticating with new key ( ok ) +``` + +### lf em 4x70 auth + +This is authentication using the autorecovery test key. + +`[usb|script] pm3 --> lf em 4x70 auth --rnd EF23C6FEEC2586 --frn 99DD8FC0` (log 1) +`[usb|script] pm3 --> lf em 4x70 auth --rnd 8713F4E00B8716 --frn CB8A1EA0` (log 2) + +#### log 1 +``` +[#] sent >>>: [ 17169 .. 55350 ] ( 38181 ) 98 bits: 00001101110111100100011110001101111111011101100001001011000011000000001001100111011101100011111100 +[#] recv <<<: [ 61421 .. 69103 ] ( 7682 ) 20 bits: 10010001110110101000 +``` + +#### decoded log 1 +``` +[#] sent >>>: 98 bits: 00 00110 1110 1111 0010 0011 1100 0110 1111 1110 1110 1100 0010 0101 1000 0110 0000000 1001 1001 1101 1101 1000 1111 1100 + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/---- FRN + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| 9 9 D D 8 F C + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| \\\\\\\---------------------------------------- Tdiv (7x zero) + || ||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/-------------------------------------------------- 56-bits RN + || ||||| E F 2 3 C 6 F E E C 2 5 8 6 + || \\\\\---------------------------------------- 0011 0 == EM4X70_COMMAND_AUTH + parity + \\---------------------------------------------- 00 == RM bits +[#] recv <<<: 20 bits: 1001 0001 1101 1010 1000 + 9 1 D A 8 ---- GRN +``` + +#### log 2 +``` +[#] sent >>>: [ 21002 .. 59178 ] ( 38176 ) 98 bits: 00001101000011100010011111101001110000000001011100001110001011000000001100101110001010000111101010 +[#] recv <<<: [ 1 .. 73322 ] ( 73321 ) 20 bits: 11110100100011100001 +``` + +#### decoded lgo 2 +``` +[#] sent >>>: 98 bits: 00 00110 1000 0111 0001 0011 1111 0100 1110 0000 0000 1011 1000 0111 0001 0110 0000000 1100 1011 1000 1010 0001 1110 1010 + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/---- FRN + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| C B 8 A 1 E A + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ||||||| + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| \\\\\\\---------------------------------------- Tdiv (7x zero) + || ||||| \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/ \--/-------------------------------------------------- 56-bits RN + || ||||| E F 2 3 C 6 F E E C 2 5 8 6 + || \\\\\---------------------------------------- 0011 0 == EM4X70_COMMAND_AUTH + parity + \\---------------------------------------------- 00 == RM bits + + +[#] recv <<<: 20 bits: 1111 0100 1000 1110 0001 + F 4 8 E 1 ---- GRN +``` + + + +#### other output + +``` +[=] Tag Auth Response: 91 DA 80 +``` + +### lf em 4x70 setpin + +Set new pin code (writes to blocks 11, 10) + +`[usb|script] pm3 --> lf em 4x70 setpin -p 12345678` + +#### log + +``` +[#] sent >>>: [ 17162 .. 19540 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 25992 .. 38282 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 45014 .. 59445 ] ( 14431 ) 37 bits: 0001010101110001100101001100100101000 +[#] recv <<<: no data +[#] sent >>>: [ 114098 .. 128531 ] ( 14433 ) 37 bits: 0001010101000101001100011111000111000 +[#] recv <<<: no data +[#] sent >>>: [ 183182 .. 210838 ] ( 27656 ) 71 bits: 00010010111100010111000111000000001001000010010001101000101011001111000 +[#] recv <<<: [ 263748 .. 276039 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 282770 .. 285147 ] ( 2377 ) 6 bits: 000010 +[#] recv <<<: [ 291612 .. 303901 ] ( 12289 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 310634 .. 313007 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 319451 .. 344030 ] ( 24579 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 +``` + +#### decoded log + +``` +[#] sent >>>: 6 bits: 00 0001 + RM EM4X70_COMMAND_ID +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 + 7 8 B 8 E 0 1 2 + +[#] sent >>>: 37 bits: 00 01010 10111 00011 00101 00110 01001 01000 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0100 0 == 0x4 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 0100 1 == 0x4 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0011 0 == 0x3 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0010 1 == 0x2 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0001 1 == 0x1 1st row nibble + row parity + || ||||| \\\\\----------------------------------- 1011 1 == 0xB address + address parity + || \\\\\------------------------------------------ 0101 0 == EM4X70_COMMAND_WRITE + parity + \\------------------------------------------------ 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 10100 01010 01100 01111 10001 11000 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 1100 0 == 0xC 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1000 1 == 0x8 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 0111 1 == 0x7 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0110 0 == 0x6 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 0101 0 == 0x5 1st row nibble + row parity + || ||||| \\\\\----------------------------------- 1010 0 == 0xA address + address parity + || \\\\\------------------------------------------ 0101 0 == EM4X70_COMMAND_WRITE + parity + \\------------------------------------------------ 00 == RM bits + +[#] sent >>>: 71 bits: 00 01001 0111 1000 1011 1000 1110 0000 0001 0010 0001 0010 0011 0100 0101 0110 0111 1000 + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| \||/ \||/ \||/ \||/ \||/ \||/ \||/ \||/-- PIN + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| 1 2 3 4 5 6 7 8 + || ||||| \||/ \||/ \||/ \||/ \||/ \||/ \||/ \||/------------------------------------------- Tag ID + || ||||| 7 8 B 8 E 0 1 2 + || \\\\\------------------------------------------------------------------------------------ EM4X70_COMMAND_PIN + parity + \\------------------------------------------------------------------------------------------ RM bits +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 Tag ID + 7 8 B 8 E 0 1 2 + +(reads the UM1 and UM2 blocks again ... skipped for brevity) +``` + + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] +[=] Writing new PIN ( ok ) +``` + +### lf em 4x70 write (lock the tag) + +This locks the tag by setting top two bits of UM1. + +`[usb|script] pm3 --> lf em 4x70 write -b 1 -d E1DF` + +#### log + +``` +[#] sent >>>: [ 17164 .. 31601 ] ( 14437 ) 37 bits: 0001010000111110100011110111111011010 +[#] recv <<<: no data +[#] sent >>>: [ 86259 .. 88633 ] ( 2374 ) 6 bits: 000001 +[#] recv <<<: [ 95089 .. 107378 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114110 .. 116487 ] ( 2377 ) 6 bits: 000010 +[#] recv <<<: [ 122952 .. 135240 ] ( 12288 ) 32 bits: 11100001110111110101011001111000 +[#] sent >>>: [ 141975 .. 144351 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150792 .. 175370 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 +``` + +#### decoded log + +``` +[#] sent >>>: 37 bits: 00 01010 00011 11101 00011 11011 11110 11010 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 1101 0 == 0xD 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1111 0 == 0xF 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 1101 1 == 0xD 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 0001 1 == 0x1 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 1110 1 == 0xE 1st row nibble + row parity + || ||||| \\\\\----------------------------------- 0001 1 == 0x1 address + address parity + || \\\\\------------------------------------------ 0101 0 == EM4X70_COMMAND_WRITE + parity + \\------------------------------------------------ 00 == RM bits + +Skipping reads of ID, UM1, UM2 +``` + + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | E1 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 1 +[=] Lockbit 1: 1 +[=] Tag is LOCKED. +[=] +``` + +### lf em 4x70 unlock + +`[usb|script] pm3 --> lf em 4x70 unlock -p 12345678` + +#### log + +``` +[#] sent >>>: [ 17162 .. 19538 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 25992 .. 38282 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 45015 .. 72674 ] ( 27659 ) 71 bits: 00010010111100010111000111000000001001000010010001101000101011001111000 +[#] recv <<<: [ 125592 .. 137883 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 144615 .. 146991 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 153457 .. 165744 ] ( 12287 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 172478 .. 174854 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 181296 .. 205874 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 +``` + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] +``` + +### lf em 4x70 setpin (revert to AAAAAAAA) + +Always leave the tag with pin `AAAAAAAA`. + +`[usb|script] pm3 --> lf em 4x70 setpin -p AAAAAAAA` + +#### log + +``` +[#] sent >>>: [ 17169 .. 19544 ] ( 2375 ) 6 bits: 000001 +[#] recv <<<: [ 25998 .. 38289 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 45021 .. 59464 ] ( 14443 ) 37 bits: 0001010101111010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 114117 .. 128558 ] ( 14441 ) 37 bits: 0001010101001010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 183212 .. 210875 ] ( 27663 ) 71 bits: 00010010111100010111000111000000001001010101010101010101010101010101010 +[#] recv <<<: [ 263791 .. 276080 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 282813 .. 285188 ] ( 2375 ) 6 bits: 000010 +[#] recv <<<: [ 291642 .. 303930 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 310664 .. 313042 ] ( 2378 ) 6 bits: 000111 +[#] recv <<<: [ 319482 .. 344060 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 +``` + +#### decoded log + +``` +[#] sent >>>: 6 bits: 00 0001 + RM EM4X70_COMMAND_ID +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 + 7 8 B 8 E 0 1 2 + +[#] sent >>>: 37 bits: 00 01010 10111 10100 10100 10100 10100 00000 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0000 0 == 0x0 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1010 0 == 0xA 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 1010 0 == 0xA 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 1010 0 == 0xA 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 1010 0 == 0xA 1st row nibble + row parity + || ||||| \\\\\----------------------------------- 1011 1 == 0xB address + address parity + || \\\\\------------------------------------------ 0101 0 == EM4X70_COMMAND_WRITE + parity + \\------------------------------------------------ 00 == RM bits + +[#] sent >>>: 37 bits: 00 01010 10100 10100 10100 10100 10100 00000 + || ||||| ||||| ||||| ||||| ||||| ||||| \\\\\---- 0000 0 == 0x0 5th row: column parity + 0 + || ||||| ||||| ||||| ||||| ||||| \\\\\---------- 1010 0 == 0xA 4th row nibble + row parity + || ||||| ||||| ||||| ||||| \\\\\---------------- 1010 0 == 0xA 3rd row nibble + row parity + || ||||| ||||| ||||| \\\\\---------------------- 1010 0 == 0xA 2nd row nibble + row parity + || ||||| ||||| \\\\\---------------------------- 1010 0 == 0xA 1st row nibble + row parity + || ||||| \\\\\----------------------------------- 1010 0 == 0xA address + address parity + || \\\\\------------------------------------------ 0101 0 == EM4X70_COMMAND_WRITE + parity + \\------------------------------------------------ 00 == RM bits + +[#] sent >>>: 71 bits: 00 01001 0111 1000 1011 1000 1110 0000 0001 0010 1010 1010 1010 1010 1010 1010 1010 1010 + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| \||/ \||/ \||/ \||/ \||/ \||/ \||/ \||/-- PIN + || ||||| |||| |||| |||| |||| |||| |||| |||| |||| A A A A A A A A + || ||||| \||/ \||/ \||/ \||/ \||/ \||/ \||/ \||/------------------------------------------- Tag ID + || ||||| 7 8 B 8 E 0 1 2 + || \\\\\------------------------------------------------------------------------------------ EM4X70_COMMAND_PIN + parity + \\------------------------------------------------------------------------------------------ RM bits +[#] recv <<<: 32 bits: 0111 1000 1011 1000 1110 0000 0001 0010 + 7 8 B 8 E 0 1 2 + +skipping read of UM1 and UM2 +``` + +#### other output + +``` +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] +[=] Writing new PIN ( ok ) +``` + + +

+ +## General format of the output: + +``` +^^^^^------- Actual bits sent +[#] DIRECTN_: [ START .. END ] ( ELAPSED) ## bits: ..... + ^^^^^^^^ ^^^^^^ .. ^^^^^^ ^^^^^^ ^^ ^^^^^ + |||||||| |||||| |||||| |||||| || \\\\\------- Actual bits sent or received + |||||||| |||||| |||||| |||||| \\------------------- Count of bits sent or received + |||||||| |||||| |||||| \\\\\\------------------------ (END - START) + |||||||| |||||| \\\\\\----------------------------------- Device tick count after last bit + |||||||| \\\\\\----------------------------------------------- Device tick count at first bit + \\\\\\\\----------------------------------------------------------- Direction: sent from reader, or received from tag +``` + +NOTE: bits sent by reader INCLUDE the 2-bit RM (always `00`) +NOTE: bits received by reader EXCLUDE the 12-bit synchronization header (`111111110000`). + + +## Initialization of the tag + +Script to initialize the tag to known starting state: + +
Hiding by default as not critical

+ +``` +rem set UM2 blocks to `AAAA` +lf em 4x70 write -b 15 -d AAAA +lf em 4x70 write -b 14 -d AAAA +lf em 4x70 write -b 13 -d AAAA +lf em 4x70 write -b 12 -d AAAA +rem set PIN code to `AAAAAAAA` +lf em 4x70 write -b 11 -d AAAA +lf em 4x70 write -b 10 -d AAAA +rem set KEY to `AAAAAAAAAAAA +lf em 4x70 write -b 9 -d AAAA +lf em 4x70 write -b 8 -d AAAA +lf em 4x70 write -b 7 -d AAAA +lf em 4x70 write -b 6 -d AAAA +lf em 4x70 write -b 5 -d AAAA +lf em 4x70 write -b 4 -d AAAA +rem set ID to `78B8E012` +lf em 4x70 write -b 3 -d 78B8 +lf em 4x70 write -b 2 -d E012 +rem set UM1 to `21DF5678` (unlocked) ... write block 1 last! +lf em 4x70 write -b 0 -d 5678 +lf em 4x70 write -b 1 -d 21DF +``` + +``` +[+] 2024-05-15T18:47:50Z remark: set UM2 blocks to `AAAA` +[#] sent >>>: [ 17156 .. 31590 ] ( 14434 ) 37 bits: 0001010111101010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86240 .. 88618 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 95070 .. 107359 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114092 .. 116470 ] ( 2378 ) 6 bits: 000010 +[#] recv <<<: [ 122934 .. 135223 ] ( 12289 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141957 .. 144333 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150774 .. 175352 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17163 .. 31601 ] ( 14438 ) 37 bits: 0001010111011010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86259 .. 88635 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 95089 .. 107379 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114111 .. 116488 ] ( 2377 ) 6 bits: 000010 +[#] recv <<<: [ 122953 .. 135241 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141976 .. 144349 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 150793 .. 175371 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17157 .. 31602 ] ( 14445 ) 37 bits: 0001010110111010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86252 .. 88627 ] ( 2375 ) 6 bits: 000001 +[#] recv <<<: [ 95081 .. 107372 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114105 .. 116478 ] ( 2373 ) 6 bits: 000010 +[#] recv <<<: [ 122934 .. 135222 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141956 .. 144332 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150775 .. 175353 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17157 .. 31592 ] ( 14435 ) 37 bits: 0001010110001010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 80480 .. 82858 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 89310 .. 101599 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 108333 .. 110708 ] ( 2375 ) 6 bits: 000010 +[#] recv <<<: [ 117161 .. 129450 ] ( 12289 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 136185 .. 138561 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 145002 .. 169581 ] ( 24579 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[+] 2024-05-15T18:47:50Z remark: set PIN code to `AAAAAAAA` +[#] sent >>>: [ 17157 .. 31593 ] ( 14436 ) 37 bits: 0001010101111010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86253 .. 88629 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 95081 .. 107372 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114104 .. 116480 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 122946 .. 135234 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141968 .. 144342 ] ( 2374 ) 6 bits: 000111 +[#] recv <<<: [ 150786 .. 175363 ] ( 24577 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17163 .. 31598 ] ( 14435 ) 37 bits: 0001010101001010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 80487 .. 82865 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 89315 .. 101606 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 108339 .. 110713 ] ( 2374 ) 6 bits: 000010 +[#] recv <<<: [ 117167 .. 129457 ] ( 12290 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 136190 .. 138563 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 145008 .. 169585 ] ( 24577 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[+] 2024-05-15T18:47:51Z remark: set KEY to `AAAAAAAAAAAA +[#] sent >>>: [ 17156 .. 31589 ] ( 14433 ) 37 bits: 0001010100101010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86242 .. 88616 ] ( 2374 ) 6 bits: 000001 +[#] recv <<<: [ 95070 .. 107359 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114093 .. 116469 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 122923 .. 135211 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141944 .. 144317 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 150761 .. 175341 ] ( 24580 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17157 .. 31588 ] ( 14431 ) 37 bits: 0001010100011010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86240 .. 88615 ] ( 2375 ) 6 bits: 000001 +[#] recv <<<: [ 95069 .. 107359 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114093 .. 116470 ] ( 2377 ) 6 bits: 000010 +[#] recv <<<: [ 122933 .. 135223 ] ( 12290 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141957 .. 144330 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 150774 .. 175352 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17157 .. 31596 ] ( 14439 ) 37 bits: 0001010011111010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86252 .. 88630 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 95082 .. 107371 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114104 .. 116478 ] ( 2374 ) 6 bits: 000010 +[#] recv <<<: [ 122933 .. 135223 ] ( 12290 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141956 .. 144332 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150775 .. 175352 ] ( 24577 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17164 .. 31603 ] ( 14439 ) 37 bits: 0001010011001010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86259 .. 88635 ] ( 2376 ) 6 bits: 000001 +[#] recv <<<: [ 95089 .. 107377 ] ( 12288 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114111 .. 116487 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 122941 .. 135229 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141963 .. 144339 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150780 .. 175359 ] ( 24579 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17163 .. 31599 ] ( 14436 ) 37 bits: 0001010010101010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86259 .. 88633 ] ( 2374 ) 6 bits: 000001 +[#] recv <<<: [ 95089 .. 107379 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114111 .. 116485 ] ( 2374 ) 6 bits: 000010 +[#] recv <<<: [ 122941 .. 135229 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141963 .. 144337 ] ( 2374 ) 6 bits: 000111 +[#] recv <<<: [ 150781 .. 175359 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17158 .. 31597 ] ( 14439 ) 37 bits: 0001010010011010010100101001010000000 +[#] recv <<<: no data +[#] sent >>>: [ 86253 .. 88631 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 95083 .. 107372 ] ( 12289 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114104 .. 116477 ] ( 2373 ) 6 bits: 000010 +[#] recv <<<: [ 122935 .. 135223 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141956 .. 144332 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 150775 .. 175352 ] ( 24577 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[+] 2024-05-15T18:47:52Z remark: set ID to `78B8E012` +[#] sent >>>: [ 17163 .. 31609 ] ( 14446 ) 37 bits: 0001010001100111110001101111000111000 +[#] recv <<<: no data +[#] sent >>>: [ 86259 .. 88637 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 95089 .. 107379 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 114112 .. 116487 ] ( 2375 ) 6 bits: 000010 +[#] recv <<<: [ 122940 .. 135230 ] ( 12290 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 141963 .. 144336 ] ( 2373 ) 6 bits: 000111 +[#] recv <<<: [ 150780 .. 175358 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17163 .. 31604 ] ( 14441 ) 37 bits: 0001010001011110100000000110010111010 +[#] recv <<<: no data +[#] sent >>>: [ 80499 .. 82872 ] ( 2373 ) 6 bits: 000001 +[#] recv <<<: [ 89328 .. 101618 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 108350 .. 110726 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 117192 .. 129481 ] ( 12289 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 136216 .. 138590 ] ( 2374 ) 6 bits: 000111 +[#] recv <<<: [ 145032 .. 169610 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[+] 2024-05-15T18:47:52Z remark: set UM1 to `21DF5678` (unlocked) ... write block 1 last! +[#] sent >>>: [ 17157 .. 31590 ] ( 14433 ) 37 bits: 0001010000000101001100011111000111000 +[#] recv <<<: no data +[#] sent >>>: [ 80482 .. 82860 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 89310 .. 101601 ] ( 12291 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 108333 .. 110709 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 117175 .. 129464 ] ( 12289 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 136198 .. 138572 ] ( 2374 ) 6 bits: 000111 +[#] recv <<<: [ 145014 .. 169592 ] ( 24578 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] + +[#] sent >>>: [ 17163 .. 31599 ] ( 14436 ) 37 bits: 0001010000110010100011110111111000010 +[#] recv <<<: no data +[#] sent >>>: [ 80486 .. 82864 ] ( 2378 ) 6 bits: 000001 +[#] recv <<<: [ 89316 .. 101606 ] ( 12290 ) 32 bits: 01111000101110001110000000010010 +[#] sent >>>: [ 108338 .. 110714 ] ( 2376 ) 6 bits: 000010 +[#] recv <<<: [ 117180 .. 129468 ] ( 12288 ) 32 bits: 00100001110111110101011001111000 +[#] sent >>>: [ 136202 .. 138578 ] ( 2376 ) 6 bits: 000111 +[#] recv <<<: [ 145020 .. 169599 ] ( 24579 ) 64 bits: 1010101010101010101010101010101010101010101010101010101010101010 + +[=] --- Tag Information --------------------------- +[=] Block | data | info +[=] ------+----------+----------------------------- +[=] 15 | AA AA | UM2 +[=] 14 | AA AA | UM2 +[=] 13 | AA AA | UM2 +[=] 12 | AA AA | UM2 +[=] ------+----------+----------------------------- +[=] 11 | -- -- | PIN write only +[=] 10 | -- -- | PIN write only +[=] ------+----------+----------------------------- +[=] 9 | -- -- | KEY write only +[=] 8 | -- -- | KEY write only +[=] 7 | -- -- | KEY write only +[=] 6 | -- -- | KEY write only +[=] 5 | -- -- | KEY write only +[=] 4 | -- -- | KEY write only +[=] ------+----------+----------------------------- +[=] 3 | 78 B8 | ID +[=] 2 | E0 12 | ID +[=] ------+----------+----------------------------- +[=] 1 | 21 DF | UM1 +[=] 0 | 56 78 | UM1 +[=] ------+----------+----------------------------- +[=] +[=] Tag ID: 78 B8 E0 12 +[=] Lockbit 0: 0 +[=] Lockbit 1: 0 +[=] Tag is UNLOCKED. +[=] +``` + +

+ +## End of document diff --git a/include/em4x70.h b/include/em4x70.h index 2a11acc02..597dbfe26 100644 --- a/include/em4x70.h +++ b/include/em4x70.h @@ -21,6 +21,7 @@ #include #include +#include #define EM4X70_NUM_BLOCKS 16 @@ -28,24 +29,36 @@ #define EM4X70_PIN_WORD_LOWER 10 #define EM4X70_PIN_WORD_UPPER 11 +/// @brief Command transport structure for EM4x70 commands. +/// @details +/// This structure is used to transport data from the PC +/// to the proxmark3, and contain all data needed for +/// a given `lf em 4x70 ...` command to be processed +/// on the proxmark3. +/// The only requirement is that this structure remain +/// smaller than the NG buffer size (256 bytes). typedef struct { - // ISSUE: `bool` type does not have a standard-defined size. - // therefore, compatibility between architectures / - // compilers is not guaranteed. - // ISSUE: C99 has no _Static_assert() ... was added in C11 - // TODO: add _Static_assert(sizeof(bool)==1); - // TODO: add _Static_assert(sizeof(em4x70_data_t)==36); bool parity; // Used for writing address uint8_t address; - // ISSUE: Presumes target is little-endian + // BUGBUG: Non-portable ... presumes stored in little-endian form! uint16_t word; // PIN to unlock + // BUGBUG: Non-portable ... presumes stored in little-endian form! uint32_t pin; // Used for authentication + // + // IoT safe subset of C++ would be helpful here, + // to support variable-bit-length integer types + // as integral integer types. + // + // Even C23 would work for this (GCC14+, Clang15+): + // _BitInt(56) rnd; + // _BitInt(28) frnd; + // _BitInt(20) grnd; uint8_t frnd[4]; uint8_t grnd[3]; uint8_t rnd[7]; @@ -54,9 +67,20 @@ typedef struct { uint8_t crypt_key[12]; // used for bruteforce the partial key - // ISSUE: Presumes target is little-endian + // BUGBUG: Non-portable ... presumes stored in little-endian form! uint16_t start_key; } em4x70_data_t; +//_Static_assert(sizeof(em4x70_data_t) == 36); + +// ISSUE: `bool` type does not have a standard-defined size. +// therefore, compatibility between architectures / +// compilers is not guaranteed. +// TODO: verify alignof(bool) == 1 +//_Static_assert(sizeof(bool) == 1, "bool size mismatch"); +typedef union { + uint8_t data[32]; +} em4x70_tag_t; +//_Static_assert(sizeof(em4x70_tag_t) == 32, "em4x70_tag_t size mismatch"); #endif /* EM4X70_H__ */ From 22b2d422d66c8f3d48763e35f9a7693b9a3fdd88 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Sun, 16 Mar 2025 01:04:02 -0700 Subject: [PATCH 04/43] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7510597a5..4dcffb995 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] +- Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) - Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) - Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo) - Changed `trace list -t seos` - improved annotation (@iceman1001) From 197cae811f35d14d4d73de7035a16b50433cf56f Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Mon, 17 Mar 2025 20:30:40 +0000 Subject: [PATCH 05/43] Update lf_t55xx_fix.lua Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/luascripts/lf_t55xx_fix.lua | 106 ++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/client/luascripts/lf_t55xx_fix.lua b/client/luascripts/lf_t55xx_fix.lua index a77cb2b9b..9a90b3f49 100644 --- a/client/luascripts/lf_t55xx_fix.lua +++ b/client/luascripts/lf_t55xx_fix.lua @@ -5,17 +5,20 @@ local os = require('os') local dash = string.rep('--', 32) local dir = os.getenv('HOME') .. '/.proxmark3/logs/' local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) +local pm3 = require('pm3') +p = pm3.pm3() local command = core.console +command('clear') author = ' Author: jareckib - 15.02.2025' -version = ' version v1.00' +version = ' version v1.01' desc = [[ This simple script first checks if a password has been set for the T5577. It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password is found, it uses the wipe command to erase the T5577. Then the reanimation procedure is applied. If the password is not found or doesn't exist the script only performs the reanimation procedure. The script revives 99% of blocked tags. - ]] +]] usage = [[ script run lf_t55xx_fix ]] @@ -44,6 +47,25 @@ local function read_log_file(logfile) return content end +local function sleep(n) + os.execute("sleep " ..tonumber(n)) +end + +function wait(msec) + local t = os.clock() + repeat + until os.clock() > t + msec * 1e-3 +end + +local function timer(n) + while n > 0 do + io.write("::::: "..ac.yellow.. tonumber(n) ..ac.yellow.." sec "..ac.reset..":::::\r") + sleep(1) + io.flush() + n = n-1 + end +end + local function extract_password(log_content) for line in log_content:gmatch("[^\r\n]+") do local password = line:match('%[%+%] found valid password: %[ (%x%x%x%x%x%x%x%x) %]') @@ -54,48 +76,66 @@ local function extract_password(log_content) return nil end -local function reanimate_t5577(password) - if password then - command('clear') - print(dash) - print(" Using found password to wipe: " .. password) - print(dash) - command('lf t55 wipe -p ' .. password) - else - command('clear') - print(dash) - print(ac.yellow.." No valid password found, proceeding with reanimation."..ac.reset) - print(dash) - end - command('lf t55 write -b 0 -d 000880E8 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -t -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -t -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -t -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -t -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --r0 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --r1 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --r2 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --r3 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000') - command('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') - command('lf t55 detect') - local file = io.open(logfile, "w+") +local function reset_log_file() + local file = io.open(logfile, "w+") file:write("") file:close() - print(dash) - print('all done!') +end + +local function reanimate_t5577(password) + if password then + p:console('lf t55 wipe -p ' .. password) + print("T5577 wiped using a password: " ..ac.green.. password ..ac.reset) + else + print(ac.yellow.."No valid password found, proceeding with reanimation."..ac.reset) + end + + p:console('lf t55 write -b 0 -d 000880E8 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -t -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -t -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -t -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -t -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --r0 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --r1 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --r2 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --r3 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') + reset_log_file() end local function main(args) for o, a in getopt.getopt(args, 'h') do if o == 'h' then return help() end end - command('lf t55 chk') + p:console('clear') + print(' I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset) + print(dash) + print("::: "..ac.cyan.."Hold on, I'm searching for a password in the dictionary"..ac.reset.." :::") + print(dash) + p:console('lf t55 chk') + timer(5) local log_content = read_log_file(logfile) local password = log_content and extract_password(log_content) or nil reanimate_t5577(password) + p:console('lf t55 detect') + timer(5) + local success = false + for line in p.grabbed_output:gmatch("[^\r\n]+") do + if line:find("000880E0") then + success = true + break + end + end + + if success then + print('Recovery of '..ac.cyan..'T5577'..ac.reset..' was successful !!!') + else + print('Recovery of '..ac.cyan..'T5577'..ac.reset..' was unsuccessful !!!') + end + print(dash) end main(args) From a776f9a0bd5e2e2a91c2146e79f3bc04ca36b3c1 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Mar 2025 08:11:06 +0100 Subject: [PATCH 06/43] style --- armsrc/em4x70.c | 146 ++++++++++----------- armsrc/pcf7931.c | 130 +++++++++--------- armsrc/pcf7931.h | 4 +- client/luascripts/lf_t55xx_fix.lua | 6 +- client/luascripts/lf_t55xx_multiwriter.lua | 16 +-- client/luascripts/paxton_clone.lua | 2 +- client/pyscripts/hf_mfu_uscuid.py | 12 +- client/src/cmdlfem4x70.c | 2 +- client/src/ui.c | 92 +++++++++---- doc/commands.json | 8 +- 10 files changed, 231 insertions(+), 187 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 39f231157..1e1b59347 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -108,13 +108,13 @@ static bool command_parity = true; // // Four of the commands send a predetermined bitstream, immediately synchronize // on the tag sending the header, and then receive a number of bits from the tag: -// +// // #define EM4X70_COMMAND_ID 0x01 // 0b0001 --> 0b001'1 // Tag: [LIW] [Header][ID₃₁..ID₀][LIW] // Reader: [RM][Command] // Bits Sent: RM + 4 bits // Bits Recv: Header + 32 bits -// +// // #define EM4X70_COMMAND_UM1 0x02 // 0b0010 --> 0b010'1 // Tag: [LIW] [Header][LB₁, LB₀, UM1₂₉..UM1₀][LIW] // Reader: [RM][Command] @@ -171,7 +171,7 @@ static bool command_parity = true; // Auto-detect tag variant and command parity? // EM4070/V4070 does not contain UM2 or PIN, and UM1 may be OTP (one-time programmable) // EM4170 added Pin and UM2, and UM1 -// +// // Thus, to check for overlap, need only check the first three commands with parity: // | CMD | P? | Bits | Safe? | Overlaps With | Notes // |-------|-----|----------|-------|------------------|------------ @@ -182,7 +182,7 @@ static bool command_parity = true; // | PIN | No | `0b0100` | N/A | | DO NOT USE ... just in case // | UM2 | No | `0b0111` | Yes | None! | Safe ... indicates no parity AND EM4170 tag type // | ID | Yes | `0b0011` | Yes | Auth w/o Parity | Safe to try ... indicates parity if successful -// | UM1 | Yes | `0b0101` | Yes | Write w/o Parity | +// | UM1 | Yes | `0b0101` | Yes | Write w/o Parity | // | AUTH | Yes | `0b0110` | Yes | None! | Not testable // | WRITE | Yes | `0b1010` | NO | None! | DO NOT USE ... just in case // | PIN | Yes | `0b1001` | N/A | None! | DO NOT USE ... just in case @@ -359,15 +359,15 @@ typedef struct _em4x70_transmit_log_t { em4x70_sublog_t receive; } em4x70_transmitted_data_log_t; em4x70_transmitted_data_log_t g_not_used_directly; // change to bigbuff allocation? -em4x70_transmitted_data_log_t* g_Log = &g_not_used_directly; +em4x70_transmitted_data_log_t *g_Log = &g_not_used_directly; static void log_reset(void) { if (g_Log != NULL) { memset(g_Log, 0, sizeof(em4x70_transmitted_data_log_t)); } } -static void log_dump_helper(em4x70_sublog_t * part, bool is_transmit) { +static void log_dump_helper(em4x70_sublog_t *part, bool is_transmit) { if (g_dbglevel >= DBG_INFO || FORCE_ENABLE_LOGGING) { - char const * const direction = is_transmit ? "sent >>>" : "recv <<<"; + char const *const direction = is_transmit ? "sent >>>" : "recv <<<"; if (part->bits_used == 0) { DPRINTF_EXTENDED(("%s: no data", direction)); } else { @@ -377,12 +377,12 @@ static void log_dump_helper(em4x70_sublog_t * part, bool is_transmit) { bitstring[i] = part->bit[i] ? '1' : '0'; } DPRINTF_EXTENDED(( - "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s", - direction, - part->start_tick, part->end_tick, - part->end_tick - part->start_tick, - part->bits_used, bitstring - )); + "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s", + direction, + part->start_tick, part->end_tick, + part->end_tick - part->start_tick, + part->bits_used, bitstring + )); } } } @@ -390,7 +390,7 @@ static void log_dump(void) { if (g_dbglevel >= DBG_INFO || FORCE_ENABLE_LOGGING) { bool hasContent = false; if (g_Log != NULL) { - uint8_t * check_for_data = (uint8_t *)g_Log; + uint8_t *check_for_data = (uint8_t *)g_Log; for (size_t i = 0; i < sizeof(em4x70_transmitted_data_log_t); ++i) { if (check_for_data[i] != 0) { hasContent = true; @@ -419,7 +419,7 @@ static void log_sent_bit_end(uint32_t end_tick) { } } static void log_received_bit_start(uint32_t start_tick) { - if (g_Log != NULL && g_Log->receive.start_tick == 0) { + if (g_Log != NULL && g_Log->receive.start_tick == 0) { g_Log->receive.start_tick = start_tick; } } @@ -475,7 +475,7 @@ static bool check_ack(void) { // ACK 64 + 64 // NAK 64 + 48 if (check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) { + check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) { // ACK return true; } @@ -516,12 +516,12 @@ typedef struct _em4x70_command_bitstream { uint8_t received_data_converted_to_bytes[(EM4X70_MAX_BITSTREAM_BITS / 8) + (EM4X70_MAX_BITSTREAM_BITS % 8 ? 1 : 0)]; } em4x70_command_bitstream_t; -typedef bool (*bitstream_command_generator_id_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); -typedef bool (*bitstream_command_generator_um1_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); -typedef bool (*bitstream_command_generator_um2_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity); -typedef bool (*bitstream_command_generator_auth_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t * rnd, const uint8_t * frnd); -typedef bool (*bitstream_command_generator_pin_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t * tag_id, const uint32_t pin_little_endian); -typedef bool (*bitstream_command_generator_write_t)(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, uint16_t data_little_endian, uint8_t address); +typedef bool (*bitstream_command_generator_id_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_um1_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_um2_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity); +typedef bool (*bitstream_command_generator_auth_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *rnd, const uint8_t *frnd); +typedef bool (*bitstream_command_generator_pin_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *tag_id, const uint32_t pin_little_endian); +typedef bool (*bitstream_command_generator_write_t)(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, uint16_t data_little_endian, uint8_t address); typedef struct _em4x70_command_generators_t { bitstream_command_generator_id_t id; @@ -534,9 +534,9 @@ typedef struct _em4x70_command_generators_t { #endif // #pragma endregion // Bitstream structures / enumerations #if 1 // #pragma region // Functions to dump bitstreams to debug output -static void bitstream_dump_helper(const em4x70_bitstream_t * bitstream, bool is_transmit) { +static void bitstream_dump_helper(const em4x70_bitstream_t *bitstream, bool is_transmit) { // mimic the log's output format to make comparisons easier - char const * const direction = is_transmit ? "sent >>>" : "recv <<<"; + char const *const direction = is_transmit ? "sent >>>" : "recv <<<"; if (bitstream->bitcount == 0) { if (g_dbglevel >= DBG_INFO || true) { DPRINTF_EXTENDED(("%s: no data", direction)); @@ -550,17 +550,17 @@ static void bitstream_dump_helper(const em4x70_bitstream_t * bitstream, bool is_ bitstring[i] = bitstream->one_bit_per_byte[i] ? '1' : '0'; } DPRINTF_EXTENDED(( - "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s%s", - direction, - 0, 0, 0, - bitstream->bitcount + (is_transmit ? 2u : 0u), // add the two RM bits to transmitted data - is_transmit ? "00" : "", // add the two RM bits to transmitted data - bitstring - )); + "%s: [ %8d .. %8d ] ( %6d ) %2d bits: %s%s", + direction, + 0, 0, 0, + bitstream->bitcount + (is_transmit ? 2u : 0u), // add the two RM bits to transmitted data + is_transmit ? "00" : "", // add the two RM bits to transmitted data + bitstring + )); } } -static void bitstream_dump(const em4x70_command_bitstream_t * cmd_bitstream) { - bitstream_dump_helper(&cmd_bitstream->to_send, true ); +static void bitstream_dump(const em4x70_command_bitstream_t *cmd_bitstream) { + bitstream_dump_helper(&cmd_bitstream->to_send, true); bitstream_dump_helper(&cmd_bitstream->to_receive, false); } #endif // #pragma region // Functions to dump bitstreams to debug output @@ -569,14 +569,14 @@ static void bitstream_dump(const em4x70_command_bitstream_t * cmd_bitstream) { /// @brief Internal function to send a bitstream to the tag. /// @details This function presumes a validated structure, and sends the bitstream without delays, to support timing-sensitive operations. /// @param send The details on the bitstream to send to the tag. -/// @return -static bool send_bitstream_internal(const em4x70_bitstream_t * send) { +/// @return +static bool send_bitstream_internal(const em4x70_bitstream_t *send) { // similar to original send_command_and_read, but using provided bitstream int retries = EM4X70_COMMAND_RETRIES; // TIMING SENSITIVE FUNCTION ... Minimize delays after finding the listen window while (retries) { - const uint8_t * s = send->one_bit_per_byte; + const uint8_t *s = send->one_bit_per_byte; uint8_t sent = 0; retries--; if (find_listen_window(true)) { // `true` will automatically send the two `RM` zero bits @@ -597,9 +597,9 @@ static bool send_bitstream_internal(const em4x70_bitstream_t * send) { /// @param recv Buffer to store received data from the tag. /// `recv->expected_bitcount` must be initialized to indicate expected bits to receive from the tag. /// @return true only if the bitstream was sent and the expected count of bits were received from the tag. -static bool send_bitstream_and_read(em4x70_command_bitstream_t * command_bitstream) { - const em4x70_bitstream_t * send = &command_bitstream->to_send; - em4x70_bitstream_t * recv = &command_bitstream->to_receive; +static bool send_bitstream_and_read(em4x70_command_bitstream_t *command_bitstream) { + const em4x70_bitstream_t *send = &command_bitstream->to_send; + em4x70_bitstream_t *recv = &command_bitstream->to_receive; // Validate the parameters before proceeding bool parameters_valid = true; @@ -614,7 +614,7 @@ static bool send_bitstream_and_read(em4x70_command_bitstream_t * command_bitstre (command_bitstream->command == EM4X70_COMMAND_UM1) || (command_bitstream->command == EM4X70_COMMAND_UM2) || (command_bitstream->command == EM4X70_COMMAND_AUTH) - ) { + ) { // These are the four commands that are supported by this function. // Allow these to proceed. } else { @@ -665,7 +665,7 @@ static bool send_bitstream_and_read(em4x70_command_bitstream_t * command_bitstre // similar to original send_command_and_read, but using provided bitstream int bits_received = 0; - + // NOTE: reset of log does not track the time first bit is sent. That occurs // when the first sent bit is recorded in the log. log_reset(); @@ -694,9 +694,9 @@ static bool send_bitstream_and_read(em4x70_command_bitstream_t * command_bitstre // finally return the result of the operation return result; } -static bool send_bitstream_wait_ack_wait_read(em4x70_command_bitstream_t * command_bitstream) { - const em4x70_bitstream_t * send = &command_bitstream->to_send; - em4x70_bitstream_t * recv = &command_bitstream->to_receive; +static bool send_bitstream_wait_ack_wait_read(em4x70_command_bitstream_t *command_bitstream) { + const em4x70_bitstream_t *send = &command_bitstream->to_send; + em4x70_bitstream_t *recv = &command_bitstream->to_receive; // Validate the parameters before proceeding bool parameters_valid = true; @@ -769,10 +769,10 @@ static bool send_bitstream_wait_ack_wait_read(em4x70_command_bitstream_t * comma return result; } -static bool send_bitstream_wait_ack_wait_ack(em4x70_command_bitstream_t * command_bitstream) { +static bool send_bitstream_wait_ack_wait_ack(em4x70_command_bitstream_t *command_bitstream) { - const em4x70_bitstream_t * send = &command_bitstream->to_send; - em4x70_bitstream_t * recv = &command_bitstream->to_receive; + const em4x70_bitstream_t *send = &command_bitstream->to_send; + em4x70_bitstream_t *recv = &command_bitstream->to_receive; // Validate the parameters before proceeding bool parameters_valid = true; @@ -835,7 +835,7 @@ static bool send_bitstream_wait_ack_wait_ack(em4x70_command_bitstream_t * comman #endif // #pragma region // Functions to send bitstreams, with options to receive data #if 1 // #pragma region // Create bitstreams for each type of EM4x70 command -static bool add_bit_to_bitstream(em4x70_bitstream_t * s, bool b) { +static bool add_bit_to_bitstream(em4x70_bitstream_t *s, bool b) { uint8_t i = s->bitcount; uint8_t bits_to_add = 1u; @@ -848,10 +848,10 @@ static bool add_bit_to_bitstream(em4x70_bitstream_t * s, bool b) { s->bitcount++; return true; } -static bool add_nibble_to_bitstream(em4x70_bitstream_t * s, uint8_t nibble, bool add_fifth_parity_bit) { +static bool add_nibble_to_bitstream(em4x70_bitstream_t *s, uint8_t nibble, bool add_fifth_parity_bit) { uint8_t i = s->bitcount; uint8_t bits_to_add = add_fifth_parity_bit ? 5u : 4u; - + if (i > EM4X70_MAX_BITSTREAM_BITS - bits_to_add) { DPRINTF_ERROR(("Too many bits to add to bitstream: %d, %d", i, bits_to_add)); return false; @@ -875,7 +875,7 @@ static bool add_nibble_to_bitstream(em4x70_bitstream_t * s, uint8_t nibble, bool s->bitcount += bits_to_add; return true; } -static bool add_byte_to_bitstream(em4x70_bitstream_t * s, uint8_t b) { +static bool add_byte_to_bitstream(em4x70_bitstream_t *s, uint8_t b) { uint8_t i = s->bitcount; uint8_t bits_to_add = 8u; @@ -897,7 +897,7 @@ static bool add_byte_to_bitstream(em4x70_bitstream_t * s, uint8_t b) { } -static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { +static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity) { const uint8_t expected_bits_to_send = 4u; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); @@ -911,7 +911,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t } return result; } -static bool create_legacy_em4x70_bitstream_for_cmd_um1(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { +static bool create_legacy_em4x70_bitstream_for_cmd_um1(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity) { const uint8_t expected_bits_to_send = 4u; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); @@ -925,7 +925,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_um1(em4x70_command_bitstream_ } return result; } -static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity) { +static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity) { const uint8_t expected_bits_to_send = 4u; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); @@ -939,14 +939,14 @@ static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_ } return true; } -static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t *rnd, const uint8_t *frnd) { +static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *rnd, const uint8_t *frnd) { const uint8_t expected_bits_to_send = 96u; bool result = true; - + memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_AUTH; - em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + em4x70_bitstream_t *s = &out_cmd_bitstream->to_send; // ********************************************************************************* // HACK -- Insert an extra zero bit to match legacy behavior @@ -994,12 +994,12 @@ static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream return result; } -static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, const uint8_t *tag_id, const uint32_t pin) { +static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *tag_id, const uint32_t pin) { const uint8_t expected_bits_to_send = 69; // normally 68 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); - em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + em4x70_bitstream_t *s = &out_cmd_bitstream->to_send; out_cmd_bitstream->command = EM4X70_COMMAND_PIN; @@ -1015,7 +1015,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_ // Send tag's ID ... indexes 4 .. 35 // e.g., tag_id points to &tag.data[4] ... &tag.data[7] for (uint_fast8_t i = 0; i < 4; i++) { - uint8_t b = tag_id[3-i]; + uint8_t b = tag_id[3 - i]; result = result && add_byte_to_bitstream(s, b); } @@ -1033,13 +1033,13 @@ static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_ } return result; } -static bool create_legacy_em4x70_bitstream_for_cmd_write(em4x70_command_bitstream_t * out_cmd_bitstream, bool with_command_parity, uint16_t new_data, uint8_t address) { +static bool create_legacy_em4x70_bitstream_for_cmd_write(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, uint16_t new_data, uint8_t address) { const uint8_t expected_bits_to_send = 35u; // normally 34 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_WRITE; - em4x70_bitstream_t * s = &out_cmd_bitstream->to_send; + em4x70_bitstream_t *s = &out_cmd_bitstream->to_send; // ********************************************************************************* // HACK -- Insert an extra zero bit to match legacy behavior @@ -1105,7 +1105,7 @@ const em4x70_command_generators_t legacy_em4x70_command_generators = { static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *response) { em4x70_command_bitstream_t auth_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->auth(&auth_cmd, command_parity, rnd, frnd); bool result = send_bitstream_and_read(&auth_cmd); @@ -1193,7 +1193,7 @@ static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t * // log entry/exit point static int send_pin(const uint32_t pin) { em4x70_command_bitstream_t send_pin_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->pin(&send_pin_cmd, command_parity, &tag.data[4], pin); bool result = send_bitstream_wait_ack_wait_read(&send_pin_cmd); @@ -1204,7 +1204,7 @@ static int send_pin(const uint32_t pin) { static int write(const uint16_t word, const uint8_t address) { em4x70_command_bitstream_t write_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->write(&write_cmd, command_parity, word, address); bool result = send_bitstream_wait_ack_wait_ack(&write_cmd); @@ -1226,10 +1226,10 @@ static bool find_listen_window(bool command) { 96 ( 64 + 32 ) 64 ( 32 + 16 +16 )*/ - if (check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && - check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD) && - check_pulse_length(get_pulse_length(FALLING_EDGE), (1 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD)) { + if (check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(RISING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), (2 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), (1 * EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD)) { if (command) { /* Here we are after the 64 duration edge. @@ -1238,7 +1238,7 @@ static bool find_listen_window(bool command) { * * I've found 32-40 field cycles works best * Allow user adjustment in range: 24-48 field cycles? - * On PM3Easy I've seen success at 24..40 field + * On PM3Easy I've seen success at 24..40 field */ WaitTicks(40 * TICKS_PER_FC); // Send RM Command @@ -1291,7 +1291,7 @@ static uint8_t encoded_bit_array_to_byte(const uint8_t *bits, int count_of_bits) */ static bool em4x70_read_id(void) { em4x70_command_bitstream_t read_id_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->id(&read_id_cmd, command_parity); bool result = send_bitstream_and_read(&read_id_cmd); @@ -1308,7 +1308,7 @@ static bool em4x70_read_id(void) { */ static bool em4x70_read_um1(void) { em4x70_command_bitstream_t read_um1_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->um1(&read_um1_cmd, command_parity); bool result = send_bitstream_and_read(&read_um1_cmd); @@ -1327,7 +1327,7 @@ static bool em4x70_read_um1(void) { */ static bool em4x70_read_um2(void) { em4x70_command_bitstream_t read_um2_cmd; - const em4x70_command_generators_t * generator = &legacy_em4x70_command_generators; + const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; generator->um2(&read_um2_cmd, command_parity); bool result = send_bitstream_and_read(&read_um2_cmd); diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index c1f2e3f7f..8eefd354e 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -43,7 +43,7 @@ #define _64T0 (CLOCK) // calculating the two possible pmc lengths, based on the clock. -4 at the end is to make sure not to increment too far -#define PMC_16T0_LEN ((128 + 127 + 16 + 32 + 33 + 16) * CLOCK/64); +#define PMC_16T0_LEN ((128 + 127 + 16 + 32 + 33 + 16) * CLOCK/64); #define PMC_32T0_LEN ((128 + 127 + 16 + 32 + 33 ) * CLOCK/64); // theshold for recognition of positive/negative slope @@ -54,13 +54,13 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { uint8_t blocks[8][16]; uint8_t *dest = BigBuf_get_addr(); uint16_t g_GraphTraceLen = BigBuf_max_traceLen(); - // limit g_GraphTraceLen to a little more than 2 data frames. + // limit g_GraphTraceLen to a little more than 2 data frames. // To make sure a complete dataframe is in the dataset. // 1 Frame is 16 Byte -> 128byte. at a T0 of 64 -> 8129 Samples per frame. // + PMC -> 384T0 --> 8576 samples required for one block // to make sure that one complete block is definitely being sampled, we need 2 times that // which is ~17.xxx samples. round up. and clamp to this value. - + // TODO: Doublecheck why this is being limited? - seems not to be needed. // g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; @@ -87,15 +87,15 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { samplePosLastEdge = 0; block_done = 0; bitPos = 0; - lastClockDuration=0; - - for (sample = 1 ; sample < g_GraphTraceLen-4; sample++) { - // condition is searching for the next edge, in the expected diretion. - //todo: without flouz - dest[sample] = (uint8_t)(dest[sample-1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter + lastClockDuration = 0; - if ( ((dest[sample] + THRESHOLD) < dest[sample-1] && expectedNextEdge == FALLING ) || - ((dest[sample] - THRESHOLD) > dest[sample-1] && expectedNextEdge == RISING )) { + for (sample = 1 ; sample < g_GraphTraceLen - 4; sample++) { + // condition is searching for the next edge, in the expected diretion. + //todo: without flouz + dest[sample] = (uint8_t)(dest[sample - 1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter + + if (((dest[sample] + THRESHOLD) < dest[sample - 1] && expectedNextEdge == FALLING) || + ((dest[sample] - THRESHOLD) > dest[sample - 1] && expectedNextEdge == RISING)) { //okay, next falling/rising edge found expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge @@ -104,14 +104,14 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { lastClockDuration = samplePosCurrentEdge - samplePosLastEdge; samplePosLastEdge = sample; - // Dbprintf("%d, %d, edge found, len: %d, nextEdge: %d", sample, dest[sample], lastClockDuration*DECIMATION, expectedNextEdge); - + // Dbprintf("%d, %d, edge found, len: %d, nextEdge: %d", sample, dest[sample], lastClockDuration*DECIMATION, expectedNextEdge); + // Switch depending on lastClockDuration length: - // 16T0 + // 16T0 if (ABS(lastClockDuration - _16T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! - if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { + if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC Dbprintf(_GREEN_("PMC 16T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); sample += PMC_16T0_LEN; // move to the sample after PMC @@ -120,44 +120,44 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { samplePosLastEdge = sample; block_done = 1; } - - // 32TO + + // 32TO } else if (ABS(lastClockDuration - _32T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC ! Dbprintf(_GREEN_("PMC 32T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - + sample += PMC_32T0_LEN; // move to the sample after PMC expectedNextEdge = FALLING; samplePosLastEdge = sample; block_done = 1; - // if no pmc, then its a normal bit. - // Check if its the second time, the edge changed if yes, then the bit is 0 + // if no pmc, then its a normal bit. + // Check if its the second time, the edge changed if yes, then the bit is 0 } else if (half_switch == 1) { bits[bitPos] = 0; // reset the edge counter to 0 half_switch = 0; bitPos++; - // if it is the first time the edge changed. No bit value will be set here, bit if the - // edge changes again, it will be. see case above. + // if it is the first time the edge changed. No bit value will be set here, bit if the + // edge changes again, it will be. see case above. } else half_switch++; - // 64T0 + // 64T0 } else if (ABS(lastClockDuration - _64T0) < TOLERANCE) { // this means, bit here is 1 bits[bitPos] = 1; bitPos++; - - // Error + + // Error } else { - // some Error. maybe check tolerances. + // some Error. maybe check tolerances. // likeley to happen in the first block. - + // In an Ideal world, this can be enabled. However, if only bad antenna field, this print will flood the output // and one might miss some "good" frames. //Dbprintf(_RED_("ERROR in demodulation") " Length last clock: %d - check threshold/tolerance/signal. Toss block", lastClockDuration*DECIMATION); @@ -168,7 +168,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { if (block_done == 1) { // Dbprintf(_YELLOW_("Block Done") " bitPos: %d, sample: %d", bitPos, sample); - + // check if it is a complete block. If bitpos <128, it means that we did not receive // a complete block. E.g. at the first start of a transmission. // only save if a complete block is being received. @@ -187,14 +187,14 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } num_blocks++; } - // now start over for the next block / first complete block. + // now start over for the next block / first complete block. bitPos = 0; block_done = 0; half_switch = 0; } - }else { - // Dbprintf("%d, %d", sample, dest[sample]); + } else { + // Dbprintf("%d, %d", sample, dest[sample]); } // one block only holds 16byte (=128 bit) and then comes the PMC. so if more bit are found than 129, there must be an issue and PMC has not been identfied... @@ -204,8 +204,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { bitPos = 0; } - } - + } + memcpy(outBlocks, blocks, 16 * num_blocks); return num_blocks; } @@ -251,10 +251,10 @@ bool IsBlock1PCF7931(const uint8_t *block) { } void ReadPCF7931(bool ledcontrol) { - + uint8_t maxBlocks = 8; // readable blocks int found_blocks = 0; // successfully read blocks - + // TODO: Why 17 byte len? 16 should be good. uint8_t memory_blocks[maxBlocks][17]; // PCF content uint8_t single_blocks[maxBlocks][17]; // PFC blocks with unknown position @@ -263,7 +263,7 @@ void ReadPCF7931(bool ledcontrol) { int single_blocks_cnt = 0; size_t n; // transmitted blocks - + //uint8_t found_0_1 = 0; // flag: blocks 0 and 1 were found int errors = 0; // error counter int tries = 0; // tries counter @@ -300,7 +300,7 @@ void ReadPCF7931(bool ledcontrol) { goto end; } - // This part was not working properly. + // This part was not working properly. // So currently the blocks are not being sorted, but at least printed. // // our logic breaks if we don't get at least two blocks @@ -403,28 +403,28 @@ void ReadPCF7931(bool ledcontrol) { end: -/* - Dbprintf("-----------------------------------------"); - Dbprintf("Memory content:"); - Dbprintf("-----------------------------------------"); - for (i = 0; i < maxBlocks; ++i) { - if (memory_blocks[i][ALLOC]) - print_result("Block", memory_blocks[i], 16); - else - Dbprintf("", i); - } - Dbprintf("-----------------------------------------"); + /* + Dbprintf("-----------------------------------------"); + Dbprintf("Memory content:"); + Dbprintf("-----------------------------------------"); + for (i = 0; i < maxBlocks; ++i) { + if (memory_blocks[i][ALLOC]) + print_result("Block", memory_blocks[i], 16); + else + Dbprintf("", i); + } + Dbprintf("-----------------------------------------"); - if (found_blocks < maxBlocks) { - Dbprintf("-----------------------------------------"); - Dbprintf("Blocks with unknown position:"); - Dbprintf("-----------------------------------------"); - for (i = 0; i < single_blocks_cnt; ++i) - print_result("Block", single_blocks[i], 16); + if (found_blocks < maxBlocks) { + Dbprintf("-----------------------------------------"); + Dbprintf("Blocks with unknown position:"); + Dbprintf("-----------------------------------------"); + for (i = 0; i < single_blocks_cnt; ++i) + print_result("Block", single_blocks[i], 16); - Dbprintf("-----------------------------------------"); - } -*/ + Dbprintf("-----------------------------------------"); + } + */ reply_mix(CMD_ACK, 0, 0, 0, 0, 0); } @@ -434,7 +434,7 @@ static void RealWritePCF7931( uint16_t init_delay, int8_t offsetPulseWidth, int8_t offsetPulsePosition, uint8_t address, uint8_t byte, uint8_t data, - bool ledcontrol){ + bool ledcontrol) { uint32_t tab[1024] = {0}; // data times frame uint32_t u = 0; @@ -512,10 +512,10 @@ static void RealWritePCF7931( * @param data : data to write */ void WritePCF7931( - uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, - uint16_t init_delay, - int8_t offsetPulseWidth, int8_t offsetPulsePosition, - uint8_t address, uint8_t byte, uint8_t data, + uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, + uint16_t init_delay, + int8_t offsetPulseWidth, int8_t offsetPulsePosition, + uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol) { if (g_dbglevel >= DBG_INFO) { @@ -550,7 +550,7 @@ void SendCmdPCF7931(uint32_t *tab, bool ledcontrol) { FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_PASSTHRU); if (ledcontrol) LED_A_ON(); - + // rescale the values to match the time of the timer below. for (u = 0; u < 500; ++u) { tab[u] = (tab[u] * 3) / 2; @@ -651,7 +651,7 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offset tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; tab[u + 2] = 88 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; - + } else { //add a bit 0 if (u == 0) tab[u] = 98 * T0_PCF + offsetPulsePosition; @@ -660,7 +660,7 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offset tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; tab[u + 2] = 24 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; - + } return true; } diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index 314fb7e3c..e412a7a66 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -19,9 +19,9 @@ #include "common.h" -typedef enum{ +typedef enum { FALLING, - RISING + RISING } EdgeType; size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol); diff --git a/client/luascripts/lf_t55xx_fix.lua b/client/luascripts/lf_t55xx_fix.lua index e9f379c1f..52af93a76 100644 --- a/client/luascripts/lf_t55xx_fix.lua +++ b/client/luascripts/lf_t55xx_fix.lua @@ -102,7 +102,7 @@ local function reanimate_t5577(password) p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000') p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000') p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000') - p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') reset_log_file() end @@ -118,7 +118,7 @@ local function main(args) print(dash) print("::: "..ac.cyan.."Hold on, I'm searching for a password in the dictionary"..ac.reset.." :::") print(dash) - p:console('lf t55 chk') + p:console('lf t55 chk') local log_content = read_log_file(logfile) local password = log_content and extract_password(log_content) or nil reanimate_t5577(password) @@ -141,4 +141,4 @@ local function main(args) print(dash) end -main(args) \ No newline at end of file +main(args) diff --git a/client/luascripts/lf_t55xx_multiwriter.lua b/client/luascripts/lf_t55xx_multiwriter.lua index cfaaeb112..669b51791 100644 --- a/client/luascripts/lf_t55xx_multiwriter.lua +++ b/client/luascripts/lf_t55xx_multiwriter.lua @@ -9,14 +9,14 @@ local command = core.console command('clear') author = ' Author: jareckib - 12.03.2025' version = ' version v1.03' -desc = [[ +desc = [[ This simple script stores 1, 2 or 3 different EM4102 on a single T5577. There is an option to enter the number engraved on the fob in decimal form. - The script can therefore be useful if the original EM4102 doesn't work but - has an engraved ID number. By entering such an ID as a single EM4102, we + The script can therefore be useful if the original EM4102 doesn't work but + has an engraved ID number. By entering such an ID as a single EM4102, we can create a working copy of our damaged fob. A tag T5577 created in this way works with the following USB readers: - + - ACM08Y - ACM26C - Sycreader R60D @@ -121,7 +121,7 @@ local function get_uid_from_user() while true do print(dash) io.write(ac.cyan .. '(1)' .. ac.reset .. ' Manual entry UID |' .. ac.cyan .. ' (2)' .. ac.reset .. ' Read via Proxmark3 ') - + local choice repeat choice = io.read() @@ -159,7 +159,7 @@ local function get_uid_from_user() io.read() while true do - reset_log_file() + reset_log_file() command('lf em 410x read') local log_content = read_log_file(logfile) local uid = extract_uid(log_content) @@ -178,7 +178,7 @@ end local function main(args) for o, a in getopt.getopt(args, 'h') do if o == 'h' then return help() end - end + end local blocks = {} local uid_count = 0 @@ -224,4 +224,4 @@ local function main(args) print(ac.green .. "Successfully written " .. uid_count .. " EM4102 UID(s) to T5577" .. ac.reset) end -main(args) \ No newline at end of file +main(args) diff --git a/client/luascripts/paxton_clone.lua b/client/luascripts/paxton_clone.lua index ee410a11c..1ece7303e 100644 --- a/client/luascripts/paxton_clone.lua +++ b/client/luascripts/paxton_clone.lua @@ -459,4 +459,4 @@ local function main(args) end end -main(args) \ No newline at end of file +main(args) diff --git a/client/pyscripts/hf_mfu_uscuid.py b/client/pyscripts/hf_mfu_uscuid.py index a477c1b16..417694a33 100644 --- a/client/pyscripts/hf_mfu_uscuid.py +++ b/client/pyscripts/hf_mfu_uscuid.py @@ -77,14 +77,14 @@ def parse_config(config: str): cfg_auth_type = config[8:10] cfg_cuid = config[12:14] cfg_memory_config = config[14:16] - + log_magic_wup = "Magic wakeup " + ("en" if cfg_magic_wup != "8500" else "dis") + "abled" + (" with config access" if cfg_magic_wup == "7AFF" else "") log_wup_style = "Magic wakeup style " + ("Gen1a 40(7)/43" if cfg_wup_style == "00" else ("GDM 20(7)/23" if cfg_wup_style == "85" else "unknown")) log_regular_available = "Config " + ("" if cfg_regular_available == "A0" else "un") + "available in regular mode" log_auth_type = "Auth type " + ("1B - PWD" if cfg_auth_type == "00" else "1A - 3DES") log_cuid = "CUID " + ("dis" if cfg_cuid == "A0" else "en") + "abled" log_memory_config = "Maximum memory configuration: " + (MEMORY_CONFIG[cfg_memory_config] if cfg_memory_config in MEMORY_CONFIG.keys() else "unknown") - + print(SUCCESS + "^^^^............................ " + log_magic_wup) print(SUCCESS + "....^^.......................... " + log_wup_style) print(SUCCESS + "......^^........................ " + log_regular_available) @@ -93,7 +93,7 @@ def parse_config(config: str): print(SUCCESS + "............^^.................. " + log_cuid) print(SUCCESS + "..............^^................ " + log_memory_config) print(SUCCESS + "................^^^^^^^^^^^^^^^^ version info") - + def try_auth_magic(enforced = False): if enforced and not (gen1a | alt): print(ERROR + "Magic wakeup required. Please select one.") @@ -158,7 +158,7 @@ elif write_backdoor != None: if len(data) % 8 != 0: print(ERROR + "Data must be a multiple of 4 bytes.") exit() - + try_auth_magic(True) for i in range(len(data) // 8): p.console("hf 14a raw -" + ("k" if i != (len(data) // 8 - 1) else "") + f"c A2{(write_backdoor_num + i):02x}{data[8*i:8*i+8]}", False, False) @@ -171,13 +171,13 @@ elif uid != None: p.console(f"hf 14a raw -kc" + ("s" if not (gen1a or alt) else "") + " 3002") block_2 = p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")[:8] uid_bytes = [int(uid[2*x:2*x+2], 16) for x in range(7)] - + bcc_0 = 0x88 ^ uid_bytes[0] ^ uid_bytes[1] ^ uid_bytes[2] new_block_0 = "" for i in range(3): new_block_0 += f"{uid_bytes[i]:02x}" new_block_0 += f"{bcc_0:02x}" - + bcc_1 = uid_bytes[3] ^ uid_bytes[4] ^ uid_bytes[5] ^ uid_bytes[6] new_block_1 = uid[6:] new_block_2 = f"{bcc_1:02x}" + block_2[2:] diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index c9b11f991..bfd7529fe 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -42,7 +42,7 @@ typedef struct _em4x70_tag_info_t { /// * UM2 does not exist on the tag /// * Pin does not exist on the tag /// * UM1 (including the lock bits) might be one-time programmable (OTP) - /// + /// /// [31] == Block 15 MSB == UM2₆₃..UM2₅₆ /// [30] == Block 15 LSB == UM2₅₅..UM2₄₈ /// [29] == Block 14 MSB == UM2₄₇..UM2₄₀ diff --git a/client/src/ui.c b/client/src/ui.c index 68cc71a43..cc6a411ce 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -186,6 +186,7 @@ static void fill_grabber(const char *string) { g_grabbed_output.ptr = tmp; g_grabbed_output.size += MAX_PRINT_BUFFER; } + int len = snprintf(g_grabbed_output.ptr + g_grabbed_output.idx, MAX_PRINT_BUFFER, "%s", string); if (len < 0 || len > MAX_PRINT_BUFFER) { // We leave current g_grabbed_output_len untouched @@ -196,24 +197,34 @@ static void fill_grabber(const char *string) { } void PrintAndLogOptions(const char *str[][2], size_t size, size_t space) { + char buff[2000] = "Options:\n"; char format[2000] = ""; size_t counts[2] = {0, 0}; - for (size_t i = 0; i < size; i++) - for (size_t j = 0 ; j < 2 ; j++) + + for (size_t i = 0; i < size; i++) { + for (size_t j = 0 ; j < 2 ; j++) { if (counts[j] < strlen(str[i][j])) { counts[j] = strlen(str[i][j]); } + } + } + for (size_t i = 0; i < size; i++) { + for (size_t j = 0; j < 2; j++) { - if (j == 0) + if (j == 0) { snprintf(format, sizeof(format), "%%%zus%%%zus", space, counts[j]); - else + } else { snprintf(format, sizeof(format), "%%%zus%%-%zus", space, counts[j]); + } + snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), format, " ", str[i][j]); } - if (i < size - 1) + + if (i < size - 1) { strncat(buff, "\n", sizeof(buff) - strlen(buff) - 1); + } } PrintAndLogEx(NORMAL, "%s", buff); } @@ -223,12 +234,14 @@ static uint8_t PrintAndLogEx_spinidx = 0; void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { // skip debug messages if client debugging is turned off i.e. 'DATA SETDEBUG -0' - if (g_debugMode == 0 && level == DEBUG) + if (g_debugMode == 0 && level == DEBUG) { return; + } // skip HINT messages if client has hints turned off i.e. 'HINT 0' - if (g_session.show_hints == false && level == HINT) + if (g_session.show_hints == false && level == HINT) { return; + } char prefix[40] = {0}; char buffer[MAX_PRINT_BUFFER] = {0}; @@ -242,17 +255,19 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { }; switch (level) { case ERR: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _RED_("!!") "] :rotating_light: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _RED_("!!") "] ", sizeof(prefix) - 1); + } stream = stderr; break; case FAILED: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _RED_("-") "] :no_entry: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _RED_("-") "] ", sizeof(prefix) - 1); + } break; case DEBUG: strncpy(prefix, "[" _BLUE_("#") "] ", sizeof(prefix) - 1); @@ -264,10 +279,11 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { strncpy(prefix, "[" _GREEN_("+") "] ", sizeof(prefix) - 1); break; case WARNING: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _CYAN_("!") "] :warning: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _CYAN_("!") "] ", sizeof(prefix) - 1); + } break; case INFO: strncpy(prefix, "[" _YELLOW_("=") "] ", sizeof(prefix) - 1); @@ -276,13 +292,15 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, spinner_emoji[PrintAndLogEx_spinidx], sizeof(prefix) - 1); PrintAndLogEx_spinidx++; - if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji)) + if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji)) { PrintAndLogEx_spinidx = 0; + } } else { strncpy(prefix, spinner[PrintAndLogEx_spinidx], sizeof(prefix) - 1); PrintAndLogEx_spinidx++; - if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner)) + if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner)) { PrintAndLogEx_spinidx = 0; + } } break; case NORMAL: @@ -306,8 +324,9 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { const char delim[2] = "\n"; // line starts with newline - if (buffer[0] == '\n') + if (buffer[0] == '\n') { fPrintAndLog(stream, ""); + } token = strtok_r(buffer, delim, &tmp_ptr); @@ -315,16 +334,21 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { size_t size = strlen(buffer2); - if (strlen(token)) + if (strlen(token)) { snprintf(buffer2 + size, sizeof(buffer2) - size, "%s%s\n", prefix, token); - else + } else { snprintf(buffer2 + size, sizeof(buffer2) - size, "\n"); + } token = strtok_r(NULL, delim, &tmp_ptr); } + fPrintAndLog(stream, "%s", buffer2); + } else { + snprintf(buffer2, sizeof(buffer2), "%s%s", prefix, buffer); + if (level == INPLACE) { // ignore INPLACE if rest of output is grabbed if (!(g_printAndLog & PRINTANDLOG_GRAB)) { @@ -354,6 +378,7 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { if (logging && g_session.incognito) { logging = 0; } + if ((g_printAndLog & PRINTANDLOG_LOG) && logging && !logfile) { char *my_logfile_path = NULL; char filename[40]; @@ -361,11 +386,15 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { time_t now = time(NULL); timenow = gmtime(&now); strftime(filename, sizeof(filename), PROXLOG, timenow); + if (searchHomeFilePath(&my_logfile_path, LOGS_SUBDIR, filename, true) != PM3_SUCCESS) { + printf(_YELLOW_("[-]") " Logging disabled!\n"); my_logfile_path = NULL; logging = 0; + } else { + logfile = fopen(my_logfile_path, "a"); if (logfile == NULL) { printf(_YELLOW_("[-]") " Can't open logfile %s, logging disabled!\n", my_logfile_path); @@ -411,13 +440,16 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { linefeed = false; buffer[strlen(buffer) - 1] = 0; } + bool filter_ansi = !g_session.supports_colors; memcpy_filter_ansi(buffer2, buffer, sizeof(buffer), filter_ansi); - if (g_printAndLog & PRINTANDLOG_PRINT) { + + if ((g_printAndLog & PRINTANDLOG_PRINT) == PRINTANDLOG_PRINT) { memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), g_session.emoji_mode); fprintf(stream, "%s", buffer3); - if (linefeed) + if (linefeed) { fprintf(stream, "\n"); + } } #ifdef RL_STATE_READCMD @@ -433,33 +465,44 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { if (((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) || (g_printAndLog & PRINTANDLOG_GRAB)) { + memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), EMO_ALTTEXT); + if (filter_ansi == false) { memcpy_filter_ansi(buffer, buffer3, sizeof(buffer3), true); } } + if ((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) { + if (filter_ansi) { fprintf(logfile, "%s", buffer3); } else { fprintf(logfile, "%s", buffer); } - if (linefeed) + + if (linefeed) { fprintf(logfile, "\n"); + } fflush(logfile); } + if (g_printAndLog & PRINTANDLOG_GRAB) { + if (filter_ansi) { fill_grabber(buffer3); } else { fill_grabber(buffer); } - if (linefeed) + + if (linefeed) { fill_grabber("\n"); + } } - if (flushAfterWrite) + if (flushAfterWrite) { fflush(stdout); + } //release lock pthread_mutex_unlock(&g_print_lock); @@ -478,9 +521,10 @@ void memcpy_filter_rlmarkers(void *dest, const void *src, size_t n) { uint8_t *rsrc = (uint8_t *)src; uint16_t si = 0; for (size_t i = 0; i < n; i++) { - if ((rsrc[i] == '\001') || (rsrc[i] == '\002')) + if ((rsrc[i] == '\001') || (rsrc[i] == '\002')) { // skip readline special markers continue; + } rdest[si++] = rsrc[i]; } } diff --git a/doc/commands.json b/doc/commands.json index 04d547610..584caef94 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -5047,7 +5047,7 @@ "-v, --verbose verbose output", "-f, --file Specify a filename for dump file", "--emu from emulator memory", - "--start index of block to start writing (default 0)", + "--start index of block to start writing (def 0)", "--end index of block to end writing (default last block)" ], "usage": "hf mf gload [-hv] [--mini] [--1k] [--1k+] [--2k] [--4k] [-p ] [-f ] [--emu] [--start ] [--end ]" @@ -10992,8 +10992,8 @@ "-r, --reset Reset configuration to default values", "-p, --pwd Password, 7bytes, LSB-order", "-d, --delay Tag initialization delay (in us)", - "--lw offset, low pulses width (in us)", - "--lp offset, low pulses position (in us)" + "--lw offset, low pulses width (in us), optional!", + "--lp offset, low pulses position (in us), optional!" ], "usage": "lf pcf7931 config [-hr] [-p ] [-d ] [--lw ] [--lp ]" }, @@ -13232,6 +13232,6 @@ "metadata": { "commands_extracted": 760, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-12T15:46:33" + "extracted_on": "2025-03-18T06:54:58" } } From 7b528a8569178a295198978478432bd8926fca43 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Mar 2025 16:48:07 +0100 Subject: [PATCH 07/43] fixed missing file in cmakelists.txt for experimental lib --- CHANGELOG.md | 1 + client/experimental_lib/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dcffb995..995cfae39 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 missing file in CMakeLists.txt (@iceman1001) - Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) - Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) - Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo) diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 87e653c11..1e17d3361 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -1,5 +1,4 @@ #----------------------------------------------------------------------------- -# Copyright (C) Jonathan Westhues, Mar 2006 # Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. # # This program is free software: you can redistribute it and/or modify @@ -414,6 +413,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/fileutils.c ${PM3_ROOT}/client/src/flash.c ${PM3_ROOT}/client/src/graph.c + ${PM3_ROOT}/client/src/hidsio.c ${PM3_ROOT}/client/src/iso4217.c ${PM3_ROOT}/client/src/jansson_path.c ${PM3_ROOT}/client/src/lua_bitlib.c From ab602794d9ee3240474528df163d417ec1f278f3 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Tue, 18 Mar 2025 22:52:19 -0700 Subject: [PATCH 08/43] rename global variable `g_command_parity` --- armsrc/em4x70.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 1e1b59347..5d65cc32f 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -44,10 +44,10 @@ #define DPRINTF_DEBUG(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_DEBUG )) { Dbprintf x ; } } while (0); #define DPRINTF_EXTENDED(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel >= DBG_EXTENDED)) { Dbprintf x ; } } while (0); #define DPRINTF_PROLIX(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel > DBG_EXTENDED)) { Dbprintf x ; } } while (0); +// EM4170 requires a parity bit on commands, other variants do not. +static bool g_command_parity = true; static em4x70_tag_t tag = { 0 }; -// EM4170 requires a parity bit on commands, other variants do not. -static bool command_parity = true; #if 1 // Calculation of ticks for timing functions @@ -1106,7 +1106,7 @@ static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *respon em4x70_command_bitstream_t auth_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->auth(&auth_cmd, command_parity, rnd, frnd); + generator->auth(&auth_cmd, g_command_parity, rnd, frnd); bool result = send_bitstream_and_read(&auth_cmd); if (result) { @@ -1194,7 +1194,7 @@ static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t * static int send_pin(const uint32_t pin) { em4x70_command_bitstream_t send_pin_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->pin(&send_pin_cmd, command_parity, &tag.data[4], pin); + generator->pin(&send_pin_cmd, g_command_parity, &tag.data[4], pin); bool result = send_bitstream_wait_ack_wait_read(&send_pin_cmd); return result ? PM3_SUCCESS : PM3_ESOFT; @@ -1205,7 +1205,7 @@ static int write(const uint16_t word, const uint8_t address) { em4x70_command_bitstream_t write_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->write(&write_cmd, command_parity, word, address); + generator->write(&write_cmd, g_command_parity, word, address); bool result = send_bitstream_wait_ack_wait_ack(&write_cmd); if (!result) { @@ -1292,7 +1292,7 @@ static uint8_t encoded_bit_array_to_byte(const uint8_t *bits, int count_of_bits) static bool em4x70_read_id(void) { em4x70_command_bitstream_t read_id_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->id(&read_id_cmd, command_parity); + generator->id(&read_id_cmd, g_command_parity); bool result = send_bitstream_and_read(&read_id_cmd); if (result) { @@ -1309,7 +1309,7 @@ static bool em4x70_read_id(void) { static bool em4x70_read_um1(void) { em4x70_command_bitstream_t read_um1_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->um1(&read_um1_cmd, command_parity); + generator->um1(&read_um1_cmd, g_command_parity); bool result = send_bitstream_and_read(&read_um1_cmd); if (result) { @@ -1328,7 +1328,7 @@ static bool em4x70_read_um1(void) { static bool em4x70_read_um2(void) { em4x70_command_bitstream_t read_um2_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->um2(&read_um2_cmd, command_parity); + generator->um2(&read_um2_cmd, g_command_parity); bool result = send_bitstream_and_read(&read_um2_cmd); if (result) { @@ -1444,7 +1444,7 @@ void em4x70_info(const em4x70_data_t *etd, bool ledcontrol) { bool success_with_UM2 = false; // Support tags with and without command parity bits - command_parity = etd->parity; + g_command_parity = etd->parity; init_tag(); em4x70_setup_read(); @@ -1472,10 +1472,10 @@ void em4x70_info(const em4x70_data_t *etd, bool ledcontrol) { void em4x70_write(const em4x70_data_t *etd, bool ledcontrol) { int status = PM3_ESOFT; - command_parity = etd->parity; + g_command_parity = etd->parity; // Disable to prevent sending corrupted data to the tag. - if (command_parity) { + if (g_command_parity) { DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 write` is non-functional and may corrupt data on the tag.")); // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); // return; @@ -1508,7 +1508,7 @@ void em4x70_unlock(const em4x70_data_t *etd, bool ledcontrol) { int status = PM3_ESOFT; - command_parity = etd->parity; + g_command_parity = etd->parity; init_tag(); em4x70_setup_read(); @@ -1543,10 +1543,10 @@ void em4x70_auth(const em4x70_data_t *etd, bool ledcontrol) { uint8_t response[3] = {0}; - command_parity = etd->parity; + g_command_parity = etd->parity; // Disable to prevent sending corrupted data to the tag. - if (command_parity) { + if (g_command_parity) { DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 auth` is non-functional.")); // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); // return; @@ -1571,10 +1571,10 @@ void em4x70_brute(const em4x70_data_t *etd, bool ledcontrol) { int status = PM3_ESOFT; uint8_t response[2] = {0}; - command_parity = etd->parity; + g_command_parity = etd->parity; // Disable to prevent sending corrupted data to the tag. - if (command_parity) { + if (g_command_parity) { DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 brute` is non-functional and may corrupt data on the tag.")); // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); // return; @@ -1599,10 +1599,10 @@ void em4x70_write_pin(const em4x70_data_t *etd, bool ledcontrol) { int status = PM3_ESOFT; - command_parity = etd->parity; + g_command_parity = etd->parity; // Disable to prevent sending corrupted data to the tag. - if (command_parity) { + if (g_command_parity) { DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 setpin` is non-functional and may corrupt data on the tag.")); // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); // return; @@ -1648,10 +1648,10 @@ void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) { int status = PM3_ESOFT; - command_parity = etd->parity; + g_command_parity = etd->parity; // Disable to prevent sending corrupted data to the tag. - if (command_parity) { + if (g_command_parity) { DPRINTF_ALWAYS(("Use of `--par` option with `lf em 4x70 setkey` is non-functional and may corrupt data on the tag.")); // reply_ng(CMD_LF_EM4X70_WRITE, PM3_ENOTIMPL, NULL, 0); // return; From a588e74e9df1ae82145b13355bba447cbf6d90f8 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Tue, 18 Mar 2025 22:54:46 -0700 Subject: [PATCH 09/43] rename global variable `g_tag` --- armsrc/em4x70.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 5d65cc32f..da6689b44 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -46,7 +46,7 @@ #define DPRINTF_PROLIX(x) do { if ((FORCE_ENABLE_LOGGING) || (g_dbglevel > DBG_EXTENDED)) { Dbprintf x ; } } while (0); // EM4170 requires a parity bit on commands, other variants do not. static bool g_command_parity = true; -static em4x70_tag_t tag = { 0 }; +static em4x70_tag_t g_tag = { 0 }; @@ -215,7 +215,7 @@ static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read); static bool find_listen_window(bool command); static void init_tag(void) { - memset(tag.data, 0x00, sizeof(tag.data)); + memset(g_tag.data, 0x00, sizeof(g_tag.data)); } static void em4x70_setup_read(void) { @@ -1194,7 +1194,7 @@ static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t * static int send_pin(const uint32_t pin) { em4x70_command_bitstream_t send_pin_cmd; const em4x70_command_generators_t *generator = &legacy_em4x70_command_generators; - generator->pin(&send_pin_cmd, g_command_parity, &tag.data[4], pin); + generator->pin(&send_pin_cmd, g_command_parity, &g_tag.data[4], pin); bool result = send_bitstream_wait_ack_wait_read(&send_pin_cmd); return result ? PM3_SUCCESS : PM3_ESOFT; @@ -1296,7 +1296,7 @@ static bool em4x70_read_id(void) { bool result = send_bitstream_and_read(&read_id_cmd); if (result) { - encoded_bit_array_to_bytes(read_id_cmd.to_receive.one_bit_per_byte, read_id_cmd.to_receive.bitcount, &tag.data[4]); + encoded_bit_array_to_bytes(read_id_cmd.to_receive.one_bit_per_byte, read_id_cmd.to_receive.bitcount, &g_tag.data[4]); } return result; } @@ -1313,7 +1313,7 @@ static bool em4x70_read_um1(void) { bool result = send_bitstream_and_read(&read_um1_cmd); if (result) { - encoded_bit_array_to_bytes(read_um1_cmd.to_receive.one_bit_per_byte, read_um1_cmd.to_receive.bitcount, &tag.data[0]); + encoded_bit_array_to_bytes(read_um1_cmd.to_receive.one_bit_per_byte, read_um1_cmd.to_receive.bitcount, &g_tag.data[0]); } bitstream_dump(&read_um1_cmd); @@ -1332,7 +1332,7 @@ static bool em4x70_read_um2(void) { bool result = send_bitstream_and_read(&read_um2_cmd); if (result) { - encoded_bit_array_to_bytes(read_um2_cmd.to_receive.one_bit_per_byte, read_um2_cmd.to_receive.bitcount, &tag.data[24]); + encoded_bit_array_to_bytes(read_um2_cmd.to_receive.one_bit_per_byte, read_um2_cmd.to_receive.bitcount, &g_tag.data[24]); } @@ -1466,7 +1466,7 @@ void em4x70_info(const em4x70_data_t *etd, bool ledcontrol) { 0; // not returning the data to the client about actual length read? - reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, data_size); + reply_ng(CMD_LF_EM4X70_INFO, status, g_tag.data, data_size); } void em4x70_write(const em4x70_data_t *etd, bool ledcontrol) { @@ -1501,7 +1501,7 @@ void em4x70_write(const em4x70_data_t *etd, bool ledcontrol) { StopTicks(); lf_finalize(ledcontrol); - reply_ng(CMD_LF_EM4X70_WRITE, status, tag.data, sizeof(tag.data)); + reply_ng(CMD_LF_EM4X70_WRITE, status, g_tag.data, sizeof(g_tag.data)); } void em4x70_unlock(const em4x70_data_t *etd, bool ledcontrol) { @@ -1534,7 +1534,7 @@ void em4x70_unlock(const em4x70_data_t *etd, bool ledcontrol) { StopTicks(); lf_finalize(ledcontrol); - reply_ng(CMD_LF_EM4X70_UNLOCK, status, tag.data, sizeof(tag.data)); + reply_ng(CMD_LF_EM4X70_UNLOCK, status, g_tag.data, sizeof(g_tag.data)); } void em4x70_auth(const em4x70_data_t *etd, bool ledcontrol) { @@ -1641,7 +1641,7 @@ void em4x70_write_pin(const em4x70_data_t *etd, bool ledcontrol) { StopTicks(); lf_finalize(ledcontrol); - reply_ng(CMD_LF_EM4X70_SETPIN, status, tag.data, sizeof(tag.data)); + reply_ng(CMD_LF_EM4X70_SETPIN, status, g_tag.data, sizeof(g_tag.data)); } void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) { @@ -1688,5 +1688,5 @@ void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) { StopTicks(); lf_finalize(ledcontrol); - reply_ng(CMD_LF_EM4X70_SETKEY, status, tag.data, sizeof(tag.data)); + reply_ng(CMD_LF_EM4X70_SETKEY, status, g_tag.data, sizeof(g_tag.data)); } From 86175416605ec333093febcf4dafc828e91948ce Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Tue, 18 Mar 2025 23:06:33 -0700 Subject: [PATCH 10/43] rename some constants to increase clarity --- armsrc/em4x70.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index da6689b44..a82e403ba 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -51,7 +51,7 @@ static em4x70_tag_t g_tag = { 0 }; #if 1 // Calculation of ticks for timing functions -// Conversion from Ticks to RF periods +// Nearly every calculation is done in terms of Field Codes (FC) aka RF periods // 1 us = 1.5 ticks // 1RF Period = 8us = 12 Ticks #define TICKS_PER_FC 12 @@ -71,10 +71,12 @@ static em4x70_tag_t g_tag = { 0 }; #define EM4X70_T_TAG_TOLERANCE (8 * TICKS_PER_FC) // Tolerance in RF periods for receive/LIW #define EM4X70_T_TAG_TIMEOUT (4 * EM4X70_T_TAG_FULL_PERIOD) // Timeout if we ever get a pulse longer than this -#define EM4X70_T_WAITING_FOR_LIW 50 // Pulses to wait for listen window -#define EM4X70_T_READ_HEADER_LEN 16 // Read header length (16 bit periods) -#define EM4X70_COMMAND_RETRIES 5 // Attempts to send/read command + +#define EM4X70_T_PULSES_TO_SEARCH_FOR_LIW 50 // Pulses to wait for listen window +#define EM4X70_T_PULSES_TO_SEARCH_FOR_HEADER_TRANSITION 16 // Read header length (16 bit periods), wait that many pulses to find transition from the 12x `1` to 4x `0` + +#define EM4X70_COMMAND_LIW_SEARCH_RETRIES 5 // Attempts to send/read command #define EM4X70_MAX_SEND_BITCOUNT 96u // Authentication == CMD(4) + NONCE(56) + DIVERGENCY(7) + FRND(28) == 6 + 56 + 35 == 56 + 41 == 95 bits (NOTE: RM(2) is handled as part of LIW detection) #define EM4X70_MAX_RECEIVE_BITCOUNT 64u // Maximum bits to receive in response to any command (NOTE: This is EXCLUDING the 16-bit header of 0b1111'1111'1111'0000) #endif // Calculation of ticks for timing functions @@ -572,7 +574,7 @@ static void bitstream_dump(const em4x70_command_bitstream_t *cmd_bitstream) { /// @return static bool send_bitstream_internal(const em4x70_bitstream_t *send) { // similar to original send_command_and_read, but using provided bitstream - int retries = EM4X70_COMMAND_RETRIES; + int retries = EM4X70_COMMAND_LIW_SEARCH_RETRIES; // only retries finding the LIW ... not the actual command // TIMING SENSITIVE FUNCTION ... Minimize delays after finding the listen window while (retries) { @@ -1218,7 +1220,7 @@ static int write(const uint16_t word, const uint8_t address) { static bool find_listen_window(bool command) { int cnt = 0; - while (cnt < EM4X70_T_WAITING_FOR_LIW) { + while (cnt < EM4X70_T_PULSES_TO_SEARCH_FOR_LIW) { /* 80 ( 64 + 16 ) 80 ( 64 + 16 ) @@ -1361,7 +1363,7 @@ static int em4x70_receive(uint8_t *bits, size_t maximum_bits_to_read) { WaitTicks(6 * EM4X70_T_TAG_FULL_PERIOD); // wait until we get the transition from 1's to 0's which is 1.5 full windows - for (int i = 0; i < EM4X70_T_READ_HEADER_LEN; i++) { + for (int i = 0; i < EM4X70_T_PULSES_TO_SEARCH_FOR_HEADER_TRANSITION; i++) { pl = get_pulse_length(edge); if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD)) { foundheader = true; From 43e026b1c96285e885dfdbc9b43745746b3dbfc9 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Tue, 18 Mar 2025 23:18:36 -0700 Subject: [PATCH 11/43] Fix LIW to RM delay * Change from 40 FC to 72 FC as default delay between detecting LIW and sending the command. * Use parity for ID, UM1, and UM2 commands * Remove extra bit hack from AUTH, PIN, and WRITE commands --- armsrc/em4x70.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index a82e403ba..180cc9c61 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -72,6 +72,7 @@ static em4x70_tag_t g_tag = { 0 }; #define EM4X70_T_TAG_TIMEOUT (4 * EM4X70_T_TAG_FULL_PERIOD) // Timeout if we ever get a pulse longer than this +#define EM4X70_T_DELAY_FROM_LIW_TO_RM (72 * TICKS_PER_FC) // Default delay from finding LIW until start sending RM bits #define EM4X70_T_PULSES_TO_SEARCH_FOR_LIW 50 // Pulses to wait for listen window #define EM4X70_T_PULSES_TO_SEARCH_FOR_HEADER_TRANSITION 16 // Read header length (16 bit periods), wait that many pulses to find transition from the 12x `1` to 4x `0` @@ -904,7 +905,8 @@ static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_ID; - uint8_t cmd = with_command_parity ? 0x3u : 0x1u; + //uint8_t cmd = with_command_parity ? 0x3u : 0x1u; + uint8_t cmd = 0x3u; result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); out_cmd_bitstream->to_receive.bitcount = 32; if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { @@ -918,7 +920,8 @@ static bool create_legacy_em4x70_bitstream_for_cmd_um1(em4x70_command_bitstream_ bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_UM1; - uint8_t cmd = with_command_parity ? 0x5u : 0x2u; + //uint8_t cmd = with_command_parity ? 0x5u : 0x2u; + uint8_t cmd = 0x5u; result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); out_cmd_bitstream->to_receive.bitcount = 32; if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { @@ -932,7 +935,8 @@ static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_ bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_UM2; - uint8_t cmd = with_command_parity ? 0xFu : 0x7u; + //uint8_t cmd = with_command_parity ? 0xFu : 0x7u; + uint8_t cmd = 0xFu; result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); out_cmd_bitstream->to_receive.bitcount = 64; if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { @@ -950,11 +954,6 @@ static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream em4x70_bitstream_t *s = &out_cmd_bitstream->to_send; - // ********************************************************************************* - // HACK -- Insert an extra zero bit to match legacy behavior - // ********************************************************************************* - result = result && add_bit_to_bitstream(s, 0); - // uint8_t cmd = with_command_parity ? 0x6u : 0x3u; uint8_t cmd = 0x6u; // HACK - always sent with cmd parity result = result && add_nibble_to_bitstream(s, cmd, false); @@ -1005,11 +1004,6 @@ static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_ out_cmd_bitstream->command = EM4X70_COMMAND_PIN; - // ********************************************************************************* - // HACK -- Insert an extra zero bit to match legacy behavior - // ********************************************************************************* - result = result && add_bit_to_bitstream(s, 0); - //uint8_t cmd = with_command_parity ? 0x9u : 0x4u; uint8_t cmd = 0x9u; // HACK - always sent with cmd parity, with extra zero bit in RM? result = result && add_nibble_to_bitstream(s, cmd, false); @@ -1043,11 +1037,6 @@ static bool create_legacy_em4x70_bitstream_for_cmd_write(em4x70_command_bitstrea em4x70_bitstream_t *s = &out_cmd_bitstream->to_send; - // ********************************************************************************* - // HACK -- Insert an extra zero bit to match legacy behavior - // ********************************************************************************* - result = result && add_bit_to_bitstream(s, 0); - //uint8_t cmd = with_command_parity ? 0xAu : 0x5u; uint8_t cmd = 0xAu; // HACK - always sent with cmd parity, with extra zero bit in RM? result = result && add_nibble_to_bitstream(s, cmd, false); @@ -1242,7 +1231,7 @@ static bool find_listen_window(bool command) { * Allow user adjustment in range: 24-48 field cycles? * On PM3Easy I've seen success at 24..40 field */ - WaitTicks(40 * TICKS_PER_FC); + WaitTicks(EM4X70_T_DELAY_FROM_LIW_TO_RM); // Send RM Command em4x70_send_bit(0); em4x70_send_bit(0); From af9338311521c1fac596aca2e4194872b4765053 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 08:56:00 +0100 Subject: [PATCH 12/43] fix #2790 - MFU OTP writes shouldnt allow for zeroing. Bad sanity check implemented, it should check that every bit doesnt try to zero out. Will do for now --- CHANGELOG.md | 1 + armsrc/iso14443a.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 995cfae39..76099dcf5 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] +- Change `hf mfu sim` deny OTP changes with all zeros (@iceman1001) - Added missing file in CMakeLists.txt (@iceman1001) - Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) - Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 9d282eee6..d8de3e553 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1730,6 +1730,18 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin if (CheckCrc14A(receivedCmd, len)) { uint8_t block = receivedCmd[1]; + + // OTP sanity check + // Quite a bad one, one should look at all individual bits and see if anyone tries be set as zero + // we cheat and do fat 00000000 check instead + if (block == 0x03) { + if (memcmp(receivedCmd + 2, "\x00\x00\x00\x00", 4) == 0) { + // OTP can't be set back to zero + // send NACK 0x0 == invalid argument, + EmSend4bit(CARD_NACK_IV); + } + } + if (block > pages) { // send NACK 0x0 == invalid argument EmSend4bit(CARD_NACK_IV); From e9cbdec9527a0c1a5302ee517342d1294255ef70 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 08:56:23 +0100 Subject: [PATCH 13/43] fix output --- client/src/cmdhflist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index d23a02507..2753f2c79 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -1829,8 +1829,8 @@ void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is uint8_t ks = cmd[pos + 3]; if (memcmp(cmd + pos + 3 + 1, "\x04\x7c\x02\x81\x00", 5) == 0) { snprintf(exp, size, "GET CHALLENGE " _WHITE_("(") " key " _MAGENTA_("%02X") " )", ks); + return; } - return; } if (memcmp(cmd + pos, "\x00\x87\x00", 3) == 0) { From 49d25920c1a85b20b5ae081bd7b03897bc107ce7 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 08:57:20 +0100 Subject: [PATCH 14/43] added support function for left shifting arrays --- common/commonutil.c | 19 +++++++++++++++---- common/commonutil.h | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/common/commonutil.c b/common/commonutil.c index 35a583988..a5b545f33 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -433,11 +433,21 @@ void xor(uint8_t *dest, const uint8_t *src, size_t n) { } } -void lsl(uint8_t *data, size_t len) { - for (size_t n = 0; n < len - 1; n++) { - data[n] = (data[n] << 1) | (data[n + 1] >> 7); +// left shift an array of length one bit +void lsl(uint8_t *d, size_t n) { + for (size_t i = 0; i < n - 1; i++) { + d[i] = (d[i] << 1) | (d[i + 1] >> 7); + } + d[n - 1] <<= 1; +} + +void lslx(uint8_t *d, size_t n, uint8_t shifts) { + for (uint8_t i = 0; i < shifts; i++) { + for (size_t j = 0; j < n - 1; j++) { + d[j] = (d[j] << 1) | (d[j + 1] >> 7); + } + d[n - 1] <<= 1; } - data[len - 1] <<= 1; } @@ -562,6 +572,7 @@ void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len) { */ size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb) { int i, end, step; + // overlap if ((src - dest) * 8 + src_offset - dest_offset > 0) { i = 0; diff --git a/common/commonutil.h b/common/commonutil.h index 2360f2a57..499674d24 100644 --- a/common/commonutil.h +++ b/common/commonutil.h @@ -132,7 +132,9 @@ void rol(uint8_t *data, const size_t n); void ror(uint8_t *data, const size_t n); void xor(uint8_t *dest, const uint8_t *src, size_t n); -void lsl(uint8_t *data, size_t len); +void lsl(uint8_t *d, size_t n); +void lslx(uint8_t *d, size_t n, uint8_t shifts); + uint32_t le24toh(const uint8_t data[3]); void htole24(uint32_t val, uint8_t data[3]); From b5c36148e194fb0baa034840004181f774150282 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 19 Mar 2025 09:00:44 +0100 Subject: [PATCH 15/43] Update bug_report.md Signed-off-by: Iceman --- .github/ISSUE_TEMPLATE/bug_report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 05020ce34..e74f58591 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,6 +21,7 @@ Have you followed the instructions properly? ie, flashed bootrom seperately fi **Describe the bug** A clear and concise description of what the bug is. +Please include text output of the bug happening. **To Reproduce** Steps to reproduce the behavior: @@ -40,7 +41,7 @@ If applicable, add screenshots to help explain your problem. - inside proxmark3 client run the following commands and paste the output here. - hw version - hw status - - data tune + - hw tune **Additional context** Add any other context about the problem here. From 784597641f7aa38d2be72797d61872ca673544b4 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 09:07:53 +0100 Subject: [PATCH 16/43] atleast try to break the simulation loop --- armsrc/thinfilm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/armsrc/thinfilm.c b/armsrc/thinfilm.c index 8603ec506..b5a06bf11 100644 --- a/armsrc/thinfilm.c +++ b/armsrc/thinfilm.c @@ -111,6 +111,12 @@ static int EmSendCmdThinfilmRaw(const uint8_t *resp, uint16_t respLen) { AT91C_BASE_SSC->SSC_THR = resp[i++]; FpgaSendQueueDelay = (uint8_t)AT91C_BASE_SSC->SSC_RHR; } + + if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { + b = (uint8_t)(AT91C_BASE_SSC->SSC_RHR); + (void)b; + } + if (BUTTON_PRESS()) break; } // Ensure that the FPGA Delay Queue is empty From 94a7d87b9d53c678b004b4cdbc856118c1621030 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 09:08:15 +0100 Subject: [PATCH 17/43] simplify logic --- armsrc/iso14443a.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index d8de3e553..3cfdc188c 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -3113,10 +3113,12 @@ int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, u size_t len = ReaderReceive(data, data_len, parity_array); uint8_t *data_bytes = (uint8_t *) data; - if (!len) { + if (len == 0) { BigBuf_free(); return 0; // DATA LINK ERROR - } else { + } + + // S-Block WTX while (len && ((data_bytes[0] & 0xF2) == 0xF2)) { uint32_t save_iso14a_timeout = iso14a_get_timeout(); @@ -3130,6 +3132,7 @@ int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, u // transmit S-Block ReaderTransmit(data_bytes, len, NULL); // retrieve the result again (with increased timeout) + data_bytes[0] = 0x00; len = ReaderReceive(data, data_len, parity_array); data_bytes = data; // restore timeout @@ -3156,8 +3159,6 @@ int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, u return -1; } - } - if (len) { // cut frame byte len -= 1; From c657ddf135e29602f0a6b29a7052ff068b5b309b Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 19 Mar 2025 09:08:36 +0100 Subject: [PATCH 18/43] Add hf mfu aesauth --- CHANGELOG.md | 7 ++-- armsrc/mifareutil.c | 4 +-- client/src/cmdhfmfu.c | 64 +++++++++++++++++++++++++++++++++ client/src/pm3line_vocabulary.h | 1 + doc/commands.json | 20 +++++++++-- doc/commands.md | 1 + 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76099dcf5..d1036dd33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ 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] -- Change `hf mfu sim` deny OTP changes with all zeros (@iceman1001) +- Added `hf mfu aesauth` based on existing UL AES support (@doegox) +- Changed `hf mfu sim` deny OTP changes with all zeros (@iceman1001) - Added missing file in CMakeLists.txt (@iceman1001) -- Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) +- Changed `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) - Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) - Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo) - Changed `trace list -t seos` - improved annotation (@iceman1001) @@ -19,7 +20,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed `mem spiffs tree` - ID is now shown in decimal (@iceman1001) - Added sample wiegand format 56bit (@iceman1001) - Changed Wiegand formats to include number of bits (@iceman1001) -- Fix compilation warning in hitagS (@iceman1001) +- Fixed compilation warning in hitagS (@iceman1001) - Added new wiegand format H800002 (@jmichelp) - Changed `Makefile.platform.sample` file - now have clear instructions for generating images for other proxmark3 hardware (@iceman1001) - Changed `doc/magic_cards_notes.md` - now contains documentation for iKey LLC's MF4 tag (@team-orangeBlue) diff --git a/armsrc/mifareutil.c b/armsrc/mifareutil.c index d19233f0a..bd51e0e1c 100644 --- a/armsrc/mifareutil.c +++ b/armsrc/mifareutil.c @@ -483,7 +483,7 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes) { // send & receive len = mifare_sendcmd(MIFARE_ULAES_AUTH_2, enc_rnd_ab, sizeof(enc_rnd_ab), resp, sizeof(resp), respPar, NULL); if (len != 19) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x - expected 19 got " _RED_("%u"), resp[0], len); + if (g_dbglevel >= DBG_INFO) Dbprintf("Cmd Error: %02x - expected 19 got " _RED_("%u"), resp[0], len); return 0; } @@ -492,7 +492,7 @@ int mifare_ultra_aes_auth(uint8_t keyno, uint8_t *keybytes) { mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_DECRYPT, sizeof(random_b), IV, resp + 1, random_b); if (memcmp(random_b, random_a, 16) != 0) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("failed authentication"); + if (g_dbglevel >= DBG_INFO) Dbprintf("failed authentication"); return 0; } diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index dc6a982b4..9ef064824 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -3892,6 +3892,69 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { return PM3_SUCCESS; } +//------------------------------------------------------------------------------- +// Ultralight AES Methods +//------------------------------------------------------------------------------- + +// Ultralight AES Authentication +// +static int CmdHF14AMfUAESAuth(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfu aesauth", + "Tests AES key on Mifare Ultralight AES tags.\n" + "If no key is specified, null key will be tried.\n" + "Key index 0: DataProtKey (default)\n" + "Key index 1: UIDRetrKey\n" + "Key index 2: OriginalityKey\n", + "hf mfu aesauth\n" + "hf mfu aesauth --key <32 bytes> --index <0..2>" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "key", "", "AES key (32 hex bytes)"), + arg_int0("i", "index", "<0..2>", "Key index, default: 0"), + arg_lit0("k", NULL, "Keep field on (only if a key is provided)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int ak_len = 0; + uint8_t authentication_key[32] = {0}; + uint8_t *authKeyPtr = authentication_key; + CLIGetHexWithReturn(ctx, 1, authentication_key, &ak_len); + int key_index = arg_get_int_def(ctx, 2, 0); + bool keep_field_on = arg_get_lit(ctx, 3); + + CLIParserFree(ctx); + + if (ak_len == 0) { + // default to null key + ak_len = 32; + } + if (ak_len != 32) { + PrintAndLogEx(WARNING, "Invalid key length"); + return PM3_EINVARG; + } + + if (key_index < 0 || key_index > 2) { + PrintAndLogEx(WARNING, "Invalid key index"); + return PM3_EINVARG; + } + + int result = ulaes_requestAuthentication(authKeyPtr, key_index, !keep_field_on); + + const char *key_type[] = { "DataProtKey", "UIDRetrKey", "OriginalityKey" }; + if (result == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Authentication with " _YELLOW_("%s") " " _GREEN_("%s") " ( " _GREEN_("ok")" )", + key_type[key_index], sprint_hex_inrow(authKeyPtr, ak_len)); + } else { + PrintAndLogEx(WARNING, "Authentication with " _YELLOW_("%s") " ( " _RED_("fail") " )", + key_type[key_index]); + } + return result; +} + /** A test function to validate that the polarssl-function works the same was as the openssl-implementation. @@ -5915,6 +5978,7 @@ static command_t CommandTable[] = { {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"}, {"cauth", CmdHF14AMfUCAuth, IfPm3Iso14443a, "Ultralight-C - Authentication"}, {"setpwd", CmdHF14AMfUCSetPwd, IfPm3Iso14443a, "Ultralight-C - Set 3DES key"}, + {"aesauth", CmdHF14AMfUAESAuth, IfPm3Iso14443a, "Ultralight-AES - Authentication"}, {"dump", CmdHF14AMfUDump, IfPm3Iso14443a, "Dump MIFARE Ultralight family tag to binary file"}, {"incr", CmdHF14AMfUIncr, IfPm3Iso14443a, "Increments Ev1/NTAG counter"}, {"info", CmdHF14AMfUInfo, IfPm3Iso14443a, "Tag information"}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 2447b924f..b51758eae 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -430,6 +430,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mfu otptear" }, { 0, "hf mfu cauth" }, { 0, "hf mfu setpwd" }, + { 0, "hf mfu aesauth" }, { 0, "hf mfu dump" }, { 0, "hf mfu incr" }, { 0, "hf mfu info" }, diff --git a/doc/commands.json b/doc/commands.json index 584caef94..f3a831927 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -7062,6 +7062,22 @@ ], "usage": "hf mfp wrp [-hv] -a [-d ]" }, + "hf mfu aesauth": { + "command": "hf mfu aesauth", + "description": "Tests AES key on Mifare Ultralight AES tags. If no key is specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey", + "notes": [ + "hf mfu aesauth", + "hf mfu aesauth --key <32 bytes> --index <0..2>" + ], + "offline": false, + "options": [ + "-h, --help This help", + "--key AES key (32 hex bytes)", + "-i, --index <0..2> Key index, default: 0", + "-k Keep field on (only if a key is provided)" + ], + "usage": "hf mfu aesauth [-hk] [--key ] [-i <0..2>]" + }, "hf mfu amiibo": { "command": "hf mfu amiibo", "description": "Tries to read all memory from amiibo tag and decrypt it", @@ -13230,8 +13246,8 @@ } }, "metadata": { - "commands_extracted": 760, + "commands_extracted": 761, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-18T06:54:58" + "extracted_on": "2025-03-19T08:33:58" } } diff --git a/doc/commands.md b/doc/commands.md index 960e40a2b..e9f37737a 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -617,6 +617,7 @@ Check column "offline" for their availability. |`hf mfu otptear `|N |`Tear-off test on OTP bits` |`hf mfu cauth `|N |`Ultralight-C - Authentication` |`hf mfu setpwd `|N |`Ultralight-C - Set 3DES key` +|`hf mfu aesauth `|N |`Ultralight-AES - Authentication` |`hf mfu dump `|N |`Dump MIFARE Ultralight family tag to binary file` |`hf mfu incr `|N |`Increments Ev1/NTAG counter` |`hf mfu info `|N |`Tag information` From 4c74704aa10be8098dd60b75bf0d17cefcb31fa2 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 09:44:02 +0100 Subject: [PATCH 19/43] remove wrong script --- client/luascripts/Paxton_clone.lua | 462 ----------------------------- 1 file changed, 462 deletions(-) delete mode 100644 client/luascripts/Paxton_clone.lua diff --git a/client/luascripts/Paxton_clone.lua b/client/luascripts/Paxton_clone.lua deleted file mode 100644 index 523eb07c3..000000000 --- a/client/luascripts/Paxton_clone.lua +++ /dev/null @@ -1,462 +0,0 @@ -local getopt = require('getopt') -local utils = require('utils') -local ac = require('ansicolors') -local os = require('os') -local dash = string.rep('--', 32) -local dir = os.getenv('HOME') .. '/.proxmark3/logs/' -local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) -local log_file_path = dir .. "Paxton_log.txt" -local nam = "" -local pm3 = require('pm3') -p = pm3.pm3() -local command = core.console -command('clear') - -author = ' Author: jareckib - 30.01.2025' -tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102' -version = ' version v1.19' -desc = [[ - The script automates the copying of Paxton fobs read - write. - It also allows manual input of data for blocks 4-7. - The third option is reading data stored in the log file and create new fob. - Additionally, the script calculates the ID for downgrading Paxton to EM4102. - - ]] -usage = [[ - script run paxton_clone -]] -arguments = [[ - script run paxton_clone -h : this help -]] - -local debug = true - -local function dbg(args) - if not DEBUG then return end - if type(args) == 'table' then - local i = 1 - while args[i] do - dbg(args[i]) - i = i+1 - end - else - print('###', args) - end -end - -local function help() - print() - print(author) - print(tutorial) - print(version) - print(desc) - print(ac.cyan..' Usage'..ac.reset) - print(usage) - print(ac.cyan..' Arguments'..ac.reset) - print(arguments) -end - -local function read_log_file(logfile) - local file = io.open(logfile, "r") - if not file then - error(" Could not open the file") - end - local content = file:read("*all") - file:close() - return content -end - -local function parse_blocks(result) - local blocks = {} - for line in result:gmatch("[^\r\n]+") do - local block_num, block_data = line:match("%[%=%]%s+%d/0x0([4-7])%s+%|%s+([0-9A-F ]+)") - if block_num and block_data then - block_num = tonumber(block_num) - block_data = block_data:gsub("%s+", "") - blocks[block_num] = block_data - end - end - return blocks -end - -local function hex_to_bin(hex_string) - local bin_string = "" - local hex_to_bin_map = { - ['0'] = "0000", ['1'] = "0001", ['2'] = "0010", ['3'] = "0011", - ['4'] = "0100", ['5'] = "0101", ['6'] = "0110", ['7'] = "0111", - ['8'] = "1000", ['9'] = "1001", ['A'] = "1010", ['B'] = "1011", - ['C'] = "1100", ['D'] = "1101", ['E'] = "1110", ['F'] = "1111" - } - for i = 1, #hex_string do - bin_string = bin_string .. hex_to_bin_map[hex_string:sub(i, i)] - end - return bin_string -end - -local function remove_last_two_bits(binary_str) - return binary_str:sub(1, #binary_str - 2) -end - -local function split_into_5bit_chunks(binary_str) - local chunks = {} - for i = 1, #binary_str, 5 do - table.insert(chunks, binary_str:sub(i, i + 4)) - end - return chunks -end - -local function remove_parity_bit(chunks) - local no_parity_chunks = {} - for _, chunk in ipairs(chunks) do - if #chunk == 5 then - table.insert(no_parity_chunks, chunk:sub(2)) - end - end - return no_parity_chunks -end - -local function convert_to_hex(chunks) - local hex_values = {} - for _, chunk in ipairs(chunks) do - if #chunk > 0 then - table.insert(hex_values, string.format("%X", tonumber(chunk, 2))) - end - end - return hex_values -end - -local function convert_to_decimal(chunks) - local decimal_values = {} - for _, chunk in ipairs(chunks) do - table.insert(decimal_values, tonumber(chunk, 2)) - end - return decimal_values -end - -local function find_until_before_f(hex_values) - local result = {} - for _, value in ipairs(hex_values) do - if value == 'F' then - break - end - table.insert(result, value) - end - return result -end - -local function process_block(block) - local binary_str = hex_to_bin(block) - binary_str = remove_last_two_bits(binary_str) - local chunks = split_into_5bit_chunks(binary_str) - local no_parity_chunks = remove_parity_bit(chunks) - return no_parity_chunks -end - -local function calculate_id_net(blocks) - local all_hex_values = {} - for _, block in ipairs(blocks) do - local hex_values = convert_to_hex(process_block(block)) - for _, hex in ipairs(hex_values) do - table.insert(all_hex_values, hex) - end - end - local selected_hex_values = find_until_before_f(all_hex_values) - if #selected_hex_values == 0 then - error(ac.red..' Error: '..ac.reset..'No valid data found in blocks 4 and 5') - end - local combined_hex = table.concat(selected_hex_values) - if not combined_hex:match("^%x+$") then - error(ac.red..' Error: '..ac.reset..'Invalid data in blocks 4 and 5') - end - local decimal_id = tonumber(combined_hex) - local stripped_hex_id = string.format("%X", decimal_id) - local padded_hex_id = string.format("%010X", decimal_id) - return decimal_id, padded_hex_id -end - -local function calculate_id_switch(blocks) - local all_decimal_values = {} - for _, block in ipairs(blocks) do - local decimal_values = convert_to_decimal(process_block(block)) - for _, dec in ipairs(decimal_values) do - table.insert(all_decimal_values, dec) - end - end - if #all_decimal_values < 15 then - error(ac.red..' Error:'..ac.reset..' Not enough data after processing blocks 4, 5, 6, and 7') - end - local id_positions = {9, 11, 13, 15, 2, 4, 6, 8} - local id_numbers = {} - for _, pos in ipairs(id_positions) do - table.insert(id_numbers, all_decimal_values[pos]) - end - local decimal_id = tonumber(table.concat(id_numbers)) - local padded_hex_id = string.format("%010X", decimal_id) - return decimal_id, padded_hex_id -end - -local function name_exists_in_log(name) - local file = io.open(log_file_path, "r") - if not file then - return false - end - local pattern = "^Name:%s*" .. name .. "%s*$" - for line in file:lines() do - if line:match(pattern) then - file:close() - return true - end - end - file:close() - return false -end - -local function log_result(blocks, em410_id, name) - local log_file = io.open(log_file_path, "a") - if log_file then - log_file:write("Name: " .. name .. "\n") - log_file:write("Date: ", os.date("%Y-%m-%d %H:%M:%S"), "\n") - for i = 4, 7 do - log_file:write(string.format("Block %d: %s\n", i, blocks[i] or "nil")) - end - log_file:write(string.format('EM4102 ID: %s\n', em410_id or "nil")) - log_file:write('--------------------------\n') - log_file:close() - print(' Log saved as: pm3/.proxmark3/logs/' ..ac.yellow..' Paxton_log.txt'..ac.reset) - else - print(" Failed to open log file for writing.") - end -end - -local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) - while true do - io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset) - local choice = io.read() - if choice == "1" then - io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..") - io.read() - print(dash) - command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846") - command("lf hitag wrbl --ht2 -p 5 -d " .. blocks[5] .. " -k BDF5E846") - command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846") - command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846") - elseif choice == "2" then - io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..") - io.read() - p:console("lf em 410x clone --id " .. padded_hex_id) - print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset) - else - print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset) - goto ask_again - end - while true do - print(dash) - io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset) - local another = io.read() - if another:lower() == "n" then - if was_option_3 then - print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist") - return - end - print() - print(ac.green .. " Saving Paxton_log file..." .. ac.reset) - while true do - io.write(" Enter a name for database (cannot be empty/duplicate): "..ac.yellow) - name = io.read() - io.write(ac.reset..'') - if name == nil or name:match("^%s*$") then - print(ac.red .. ' ERROR:'..ac.reset..' Name cannot be empty.') - else - if name_exists_in_log(name) then - print(ac.yellow .. ' Name exists!!! '..ac.reset.. 'Please choose a different name.') - else - break - end - end - end - log_result(blocks, padded_hex_id, name) - print(ac.green .. " Log saved successfully!" .. ac.reset) - local file = io.open(logfile, "w+") - file:write("") - file:close() - return - elseif another:lower() == "y" then - goto ask_again - else - print(ac.yellow.." Invalid response."..ac.reset.." Please enter"..ac.cyan.." y"..ac.reset.." or"..ac.cyan.." n"..ac.reset) - end - end - ::ask_again:: - end -end - -local function is_valid_hex(input) - return #input == 8 and input:match("^[0-9A-Fa-f]+$") -end - -local function main(args) - while true do - for o, a in getopt.getopt(args, 'h') do - if o == 'h' then return help() end - end - command('clear') - print(dash) - print(ac.green .. ' Select option: ' .. ac.reset) - print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy') - print(ac.cyan .. ' 2' .. ac.reset .. ' - Manually input data for Paxton blocks 4-7') - print(ac.cyan .. " 3" .. ac.reset .. " - Search in Paxton_log by name and use the data") - print(dash) - while true do - io.write(' Your choice '..ac.cyan..'(1/2/3): ' .. ac.reset) - input_option = io.read() - if input_option == "1" or input_option == "2" or input_option == "3" then - break - else - print(ac.yellow .. ' Invalid choice.' .. ac.reset .. ' Please enter ' .. ac.cyan .. '1' .. ac.reset .. ' or ' .. ac.cyan .. '2' .. ac.reset..' or'..ac.cyan..' 3'..ac.reset) - end - end - local was_option_3 = false - if input_option == "1" then - local show_place_message = true - while true do - if show_place_message then - io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') - end - io.read() - print(dash) - p:console('lf hitag read --ht2 -k BDF5E846') - if not logfile then - error(" No files in this directory") - end - local result = read_log_file(logfile) - local blocks = parse_blocks(result) - local empty_block = false - for i = 4, 7 do - if not blocks[i] then - empty_block = true - break - end - end - if empty_block then - io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') - show_place_message = false - else - print(' Readed blocks:') - print() - for i = 4, 7 do - if blocks[i] then - print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset)) - end - end - local decimal_id, padded_hex_id - if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then - print(dash) - print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset) - decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]}) - else - print(dash) - print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) - decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) - end - print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) - print(dash) - handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) - break - end - end - elseif input_option == "2" then - local blocks = {} - for i = 4, 7 do - while true do - io.write(ac.reset..' Enter data for block ' .. i .. ': ' .. ac.yellow) - local input = io.read() - input = input:upper() - if is_valid_hex(input) then - blocks[i] = input - break - else - print(ac.yellow .. ' Invalid input.' .. ac.reset .. ' Each block must be 4 bytes (8 hex characters).') - end - end - end - local decimal_id, padded_hex_id - if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then - print(ac.reset.. dash) - print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset) - decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]}) - else - print(ac.reset.. dash) - print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) - decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) - end - print(dash) - print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) - print(dash) - if not padded_hex_id then - print(ac.red..' ERROR: '..ac.reset.. 'Invalid block data provided') - return - end - handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) - break - elseif input_option == "3" then - was_option_3 = true - local retries = 3 - while retries > 0 do - io.write(' Enter the name to search ('..retries..' attempts) : '..ac.yellow) - local user_input = io.read() - io.write(ac.reset..'') - if user_input == nil or user_input:match("^%s*$") then - print(ac.yellow..' Error: '..ac.reset.. 'Empty name !!!') - end - local name_clean = "^Name:%s*" .. user_input:gsub("%s", "%%s") .. "%s*$" - local file = io.open(log_file_path, "r") - if not file then - print(ac.red .. ' Error:'..ac.reset.. 'Could not open log file.') - return - end - local lines = {} - for line in file:lines() do - table.insert(lines, line) - end - file:close() - local found = false - for i = 1, #lines do - if lines[i]:match(name_clean) then - nam = user_input - local blocks = { - [4] = lines[i + 2]:match("Block 4: (.+)"), - [5] = lines[i + 3]:match("Block 5: (.+)"), - [6] = lines[i + 4]:match("Block 6: (.+)"), - [7] = lines[i + 5]:match("Block 7: (.+)") - } - local em4102_id = lines[i + 6]:match("EM4102 ID: (.+)") - print(dash) - print(' I found the data under the name: '..ac.yellow ..nam.. ac.reset) - for j = 4, 7 do - print(string.format(" Block %d: %s%s%s", j, ac.yellow, blocks[j] or "N/A", ac.reset)) - end - print(" EM4102 ID: " .. ac.green .. (em4102_id or "N/A") .. ac.reset) - print(dash) - local decimal_id, padded_hex_id = em4102_id, em4102_id - handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3, nam) - found = true - break - end - end - if not found then - retries = retries - 1 - else - break - end - end - if retries == 0 then - print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset) - end - end - print(dash) - print(' Exiting script Lua...') - return - end -end - -main(args) From 3d0c8cab5cd39ae7c60a1d515b78dc5a49f751b1 Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:09:21 +0800 Subject: [PATCH 20/43] Refactor Hitag low-level functions into hitag_common --- armsrc/Makefile | 2 +- armsrc/appmain.c | 4 +- armsrc/hitagS.c | 678 +++---------------------------------- armsrc/hitagS.h | 2 +- armsrc/hitag_common.c | 539 +++++++++++++++++++++++++++++ armsrc/hitag_common.h | 57 ++++ client/src/cmdlfhitag.c | 3 - client/src/cmdlfhitag.h | 1 - client/src/cmdlfhitaghts.c | 75 ++-- client/src/cmdlfhitaghts.h | 3 +- client/src/cmdtrace.c | 1 + include/hitag.h | 15 +- 12 files changed, 696 insertions(+), 684 deletions(-) create mode 100644 armsrc/hitag_common.c create mode 100644 armsrc/hitag_common.h diff --git a/armsrc/Makefile b/armsrc/Makefile index b71614c83..6ff1455de 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -72,7 +72,7 @@ else endif ifneq (,$(findstring WITH_HITAG,$(APP_CFLAGS))) - SRC_HITAG = hitag2_crypto.c hitag2.c hitagS.c hitag2_crack.c + SRC_HITAG = hitag2_crypto.c hitag_common.c hitag2.c hitagS.c hitag2_crack.c APP_CFLAGS += -I../common/hitag2 else SRC_HITAG = diff --git a/armsrc/appmain.c b/armsrc/appmain.c index a32024223..e4e903a45 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1200,7 +1200,7 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_LF_HITAGS_SIMULATE: { // Simulate Hitag s tag, args = memory content - hts_simulate((bool)packet->oldarg[0], packet->data.asBytes, true); + hts_simulate((bool)packet->oldarg[0], packet->oldarg[1], packet->data.asBytes, true); break; } case CMD_LF_HITAGS_TEST_TRACES: { // Tests every challenge within the given file @@ -1218,7 +1218,7 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_LF_HITAGS_UID: { - hts_read_uid(NULL, false, true); + hts_read_uid(NULL, true, true); break; } case CMD_LF_HITAG2_WRITE: { diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 7a42fcd81..6a9f71969 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -19,6 +19,7 @@ //----------------------------------------------------------------------------- #include "hitagS.h" +#include "hitag_common.h" #include "proxmark3_arm.h" #include "cmd.h" @@ -33,12 +34,8 @@ #include "lfadc.h" #include "crc.h" #include "protocols.h" -#include "hitag.h" #include "appmain.h" // tearoff_hook() -#define CRC_PRESET 0xFF -#define CRC_POLYNOM 0x1D - static struct hitagS_tag tag = { .data.pages = { // Plain mode: | Authentication mode: @@ -58,13 +55,6 @@ static uint8_t page_to_be_written = 0; static int block_data_left = 0; static bool enable_page_tearoff = false; -typedef enum modulation { - AC2K = 0, - AC4K, - MC4K, - MC8K -} MOD; - static uint8_t protocol_mode = HITAGS_UID_REQ_ADV1; static MOD m = AC2K; // used modulation static uint32_t reader_selected_uid; @@ -72,9 +62,6 @@ static int rotate_uid = 0; static int sof_bits; // number of start-of-frame bits static uint8_t pwdh0, pwdl0, pwdl1; // password bytes static uint8_t rnd[] = {0x85, 0x44, 0x12, 0x74}; // random number -static uint16_t timestamp_high = 0; // Timer Counter 2 overflow count, ~47min - -#define TIMESTAMP ( (AT91C_BASE_TC2->TC_SR & AT91C_TC_COVFS) ? timestamp_high += 1 : 0, ((timestamp_high << 16) + AT91C_BASE_TC2->TC_CV) / T0) //#define SENDBIT_TEST @@ -98,60 +85,6 @@ datasheet HitagS_V11.pdf bytes in tables printed 3 2 1 0 #define ht2bs_4b(a,b,c,d) (~(((d|c)&(a^b))^(d|a|b))) #define ht2bs_5c(a,b,c,d,e) (~((((((c^e)|d)&a)^b)&(c^b))^(((d^e)|a)&((d^b)|c)))) -// Sam7s has several timers, we will use the source TIMER_CLOCK3 (aka AT91C_TC_CLKS_TIMER_DIV3_CLOCK) -// TIMER_CLOCK3 = MCK/32, MCK is running at 48 MHz, Timer is running at 48MHz/32 = 1500 KHz -// Hitag units (T0) have duration of 8 microseconds (us), which is 1/125000 per second (carrier) -// T0 = TIMER_CLOCK3 / 125000 = 12 - -#define T0 12 - -#define HITAG_FRAME_LEN 20 - -// TC0 and TC1 are 16-bit counters and will overflow after 5461 * T0 -// Ensure not to set these timings above 5461 (~43ms) when comparing without considering overflow, as they will never reach that value. - -#define HITAG_T_STOP 36 /* T_EOF should be > 36 */ -#define HITAG_T_LOW 8 /* T_LOW should be 4..10 */ -#define HITAG_T_0_MIN 15 /* T[0] should be 18..22 */ -#define HITAG_T_1_MIN 25 /* T[1] should be 26..30 */ -#define HITAG_T_0 20 /* T[0] should be 18..22 */ -#define HITAG_T_1 28 /* T[1] should be 26..30 */ -// #define HITAG_T_EOF 40 /* T_EOF should be > 36 */ -#define HITAG_T_EOF 80 /* T_EOF should be > 36 */ -#define HITAG_T_WAIT_RESP 200 /* T_wresp should be 204..212 */ -#define HITAG_T_WAIT_SC 200 /* T_wsc should be 90..5000 */ -#define HITAG_T_WAIT_FIRST 300 /* T_wfc should be 280..565 (T_ttf) */ -#define HITAG_T_PROG_MAX 750 /* T_prog should be 716..726 */ - -#define HITAG_T_TAG_ONE_HALF_PERIOD 10 -#define HITAG_T_TAG_TWO_HALF_PERIOD 25 -#define HITAG_T_TAG_THREE_HALF_PERIOD 41 -#define HITAG_T_TAG_FOUR_HALF_PERIOD 57 - -#define HITAG_T_TAG_HALF_PERIOD 16 -#define HITAG_T_TAG_FULL_PERIOD 32 - -#define HITAG_T_TAG_CAPTURE_ONE_HALF 13 -#define HITAG_T_TAG_CAPTURE_TWO_HALF 25 -#define HITAG_T_TAG_CAPTURE_THREE_HALF 41 -#define HITAG_T_TAG_CAPTURE_FOUR_HALF 57 - -/* - * Implementation of the crc8 calculation from Hitag S - * from http://www.proxmark.org/files/Documents/125%20kHz%20-%20Hitag/HitagS.V11.pdf - */ -static void calc_crc(unsigned char *crc, unsigned char data, unsigned char Bitcount) { - *crc ^= data; // crc = crc (exor) data - do { - if (*crc & 0x80) { // if (MSB-CRC == 1) - *crc <<= 1; // CRC = CRC Bit-shift left - *crc ^= CRC_POLYNOM; // CRC = CRC (exor) CRC_POLYNOM - } else { - *crc <<= 1; // CRC = CRC Bit-shift left - } - } while (--Bitcount); -} - static void update_tag_max_page(void) { //check which memorysize this tag has if (tag.data.s.config.MEMT == 0x00) { @@ -165,254 +98,6 @@ static void update_tag_max_page(void) { } } -static void hitag_send_bit(int bit, bool ledcontrol) { - - if (ledcontrol) LED_A_ON(); - // Reset clock for the next bit - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - - switch (m) { - case AC2K: { - if (bit == 0) { - // AC Coding --__ - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 64) {}; - - } else { - // AC coding -_-_ - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; - - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 48) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 64) {}; - - } - if (ledcontrol) LED_A_OFF(); - break; - } - case AC4K: { - if (bit == 0) { - // AC Coding --__ - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_TAG_HALF_PERIOD) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_TAG_FULL_PERIOD) {}; - - } else { - // AC coding -_-_ - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 24) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; - } - if (ledcontrol) LED_A_OFF(); - break; - } - case MC4K: { - if (bit == 0) { - // Manchester: Unloaded, then loaded |__--| - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; - - } else { - // Manchester: Loaded, then unloaded |--__| - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; - - } - if (ledcontrol) LED_A_OFF(); - break; - } - case MC8K: { - if (bit == 0) { - // Manchester: Unloaded, then loaded |__--| - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; - - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - } else { - // Manchester: Loaded, then unloaded |--__| - HIGH(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; - - LOW(GPIO_SSC_DOUT); - while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; - - } - if (ledcontrol) LED_A_OFF(); - break; - } - default: { - break; - } - } -} - -static void hitag_send_frame(const uint8_t *frame, size_t frame_len, bool ledcontrol) { - - DBG Dbprintf("hitag_send_frame: (%i) %02X %02X %02X %02X", frame_len, frame[0], frame[1], frame[2], frame[3]); - - // The beginning of the frame is hidden in some high level; pause until our bits will have an effect - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - HIGH(GPIO_SSC_DOUT); - switch (m) { - case AC4K: - case MC8K: { - while (AT91C_BASE_TC0->TC_CV < T0 * 40) {}; //FADV - break; - } - case AC2K: - case MC4K: { - while (AT91C_BASE_TC0->TC_CV < T0 * 20) {}; //STD + ADV - break; - } - } - - // SOF - send start of frame - for (size_t i = 0; i < sof_bits; i++) { - hitag_send_bit(1, ledcontrol); - } - - // Send the content of the frame - for (size_t i = 0; i < frame_len; i++) { - hitag_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1, ledcontrol); - } - - LOW(GPIO_SSC_DOUT); -} - -static void hitag_reader_send_bit(int bit, bool ledcontrol) { - - if (ledcontrol) LED_A_ON(); - // Reset clock for the next bit - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - while (AT91C_BASE_TC0->TC_CV != 0) {}; - - // Binary puls length modulation (BPLM) is used to encode the data stream - // This means that a transmission of a one takes longer than that of a zero - - HIGH(GPIO_SSC_DOUT); - -#ifdef SENDBIT_TEST - // Wait for 4-10 times the carrier period - while (AT91C_BASE_TC0->TC_CV < T0 * 6) {}; - - LOW(GPIO_SSC_DOUT); - - if (bit == 0) { - // Zero bit: |_-| - while (AT91C_BASE_TC0->TC_CV < T0 * 11) {}; - } else { - // One bit: |_--| - while (AT91C_BASE_TC0->TC_CV < T0 * 14) {}; - } -#else - // Wait for 4-10 times the carrier period - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_LOW) {}; - - LOW(GPIO_SSC_DOUT); - - if (bit == 0) { - // Zero bit: |_-| - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_0) {}; - } else { - // One bit: |_--| - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_1) {}; - } -#endif - - if (ledcontrol) LED_A_OFF(); -} - -static void hitag_reader_send_frame(const uint8_t *frame, size_t frame_len, bool ledcontrol) { - // Send the content of the frame - for (size_t i = 0; i < frame_len; i++) { - hitag_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1, ledcontrol); - } - // send EOF - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - while (AT91C_BASE_TC0->TC_CV != 0) {}; - - HIGH(GPIO_SSC_DOUT); - - // Wait for 4-10 times the carrier period - while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_LOW) {}; - - LOW(GPIO_SSC_DOUT); -} - -static void hts_stop_clock(void) { - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; - AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKDIS; -} - -static void hts_init_clock(void) { - // Enable Peripheral Clock for - // Timer Counter 0, used to measure exact timing before answering - // Timer Counter 1, used to capture edges of the tag frames - // Timer Counter 2, used to log trace time - AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1) | (1 << AT91C_ID_TC2); - - AT91C_BASE_PIOA->PIO_BSR = GPIO_SSC_FRAME; - - // Disable timer during configuration - hts_stop_clock(); - - // TC0: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; - - // TC1: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), TIOA is external trigger, - AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK // use MCK/32 (TIMER_CLOCK3) - | AT91C_TC_ABETRG // TIOA is used as an external trigger - | AT91C_TC_ETRGEDG_FALLING // external trigger on falling edge - | AT91C_TC_LDRA_RISING // load RA on on rising edge of TIOA - | AT91C_TC_LDRB_FALLING; // load RB on on falling edge of TIOA - - // TC2: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers - AT91C_BASE_TC2->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; - - // Enable and reset counters - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - - // Assert a sync signal. This sets all timers to 0 on next active clock edge - AT91C_BASE_TCB->TCB_BCR = 1; - - // synchronized startup procedure - // In theory, with MCK/32, we shouldn't be waiting longer than 32 instruction statements, right? - while (AT91C_BASE_TC0->TC_CV != 0) {}; // wait until TC0 returned to zero - - // reset timestamp - timestamp_high = 0; -} - /* * to check if the right uid was selected */ @@ -506,7 +191,6 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, DBG DbpString("SELECT"); if ((rx[0] & 0xf8) == HITAGS_SELECT && check_select(rx, BSWAP_32(tag.data.s.uid_le)) == 1) { - DBG DbpString("SELECT match"); //if the right tag was selected @@ -519,13 +203,8 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, if (protocol_mode != HITAGS_UID_REQ_STD) { //add crc8 + crc = CRC8Hitag1Bits(tx, 32); *txlen += 8; - crc = CRC_PRESET; - - for (int i = 0; i < 4; i++) { - calc_crc(&crc, tx[i], 8); - } - tx[4] = crc; } } @@ -546,26 +225,31 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, ht2_hitag2_byte(&state); } - //send con2, pwdh0, pwdl0, pwdl1 encrypted as a response - tx[0] = ht2_hitag2_byte(&state) ^ tag.data.pages[HITAGS_CONFIG_PADR][2]; - tx[1] = ht2_hitag2_byte(&state) ^ tag.data.s.config.pwdh0; - tx[2] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl0; - tx[3] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl1; + // store plaintext first + tx[0] = tag.data.pages[HITAGS_CONFIG_PADR][2]; + tx[1] = tag.data.s.config.pwdh0; + tx[2] = tag.data.s.pwdl0; + tx[3] = tag.data.s.pwdl1; if (protocol_mode != HITAGS_UID_REQ_STD) { - //add crc8 + // add crc8 *txlen += 8; - crc = CRC_PRESET; - calc_crc(&crc, tag.data.pages[HITAGS_CONFIG_PADR][2], 8); - calc_crc(&crc, tag.data.s.config.pwdh0, 8); - calc_crc(&crc, tag.data.s.pwdl0, 8); - calc_crc(&crc, tag.data.s.pwdl1, 8); - tx[4] = (crc ^ ht2_hitag2_byte(&state)); + crc = CRC8Hitag1Bits(tx, 32); + tx[4] = crc; } + + // then xor with keystream + tx[0] ^= ht2_hitag2_byte(&state); + tx[1] ^= ht2_hitag2_byte(&state); + tx[2] ^= ht2_hitag2_byte(&state); + tx[3] ^= ht2_hitag2_byte(&state); + if (protocol_mode != HITAGS_UID_REQ_STD) { + tx[4] ^= ht2_hitag2_byte(&state); + } + /* * some readers do not allow to authenticate multiple times in a row with the same tag. * use this to change the uid between authentications. - if (rotate_uid % 2 == 0) { tag.data.s.uid_le = 0x44332211; } else { @@ -621,11 +305,8 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, if (protocol_mode != HITAGS_UID_REQ_STD) { //add crc8 + crc = CRC8Hitag1Bits(tx, 32); *txlen += 8; - crc = CRC_PRESET; - for (int i = 0; i < 4; i++) { - calc_crc(&crc, tx[i], 8); - } tx[4] = crc; } @@ -643,10 +324,7 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, if (protocol_mode != HITAGS_UID_REQ_STD) { //add crc8 - crc = CRC_PRESET; - for (int i = 0; i < *txlen / 8; i++) { - calc_crc(&crc, tx[i], 8); - } + crc = CRC8Hitag1Bits(tx, *txlen); *txlen += 8; tx[16] = crc; } @@ -692,26 +370,17 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, /* * Emulates a Hitag S Tag with the given data from the .hts file */ -void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) { - - StopTicks(); - +void hts_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol) { int overflow = 0; - uint8_t rx[HITAG_FRAME_LEN]; + uint8_t rx[HITAG_FRAME_LEN] = {0}; size_t rxlen = 0; uint8_t tx[HITAG_FRAME_LEN]; size_t txlen = 0; - // Reset the received frame, frame count and timing info - memset(rx, 0x00, sizeof(rx)); - // free eventually allocated BigBuf memory BigBuf_free(); BigBuf_Clear_ext(false); - // Enable tracing - set_tracing(true); - DbpString("Starting Hitag S simulation"); tag.pstate = HT_READY; @@ -738,67 +407,16 @@ void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) { ); } - - // Set up simulator mode, frequency divisor which will drive the FPGA - // and analog mux selection. - FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + hitag_setup_fpga(0, threshold, ledcontrol); FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT); - FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz - SetAdcMuxFor(GPIO_MUXSEL_LOPKD); - - // Configure output pin that is connected to the FPGA (for modulating) - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - - // Disable modulation at default, which means release resistance - LOW(GPIO_SSC_DOUT); - - hts_init_clock(); - - if (ledcontrol) LED_D_ON(); while ((BUTTON_PRESS() == false) && (data_available() == false)) { uint32_t start_time = 0; WDT_HIT(); - // Receive frame, watch for at most T0*EOF periods - while (AT91C_BASE_TC1->TC_CV < T0 * HITAG_T_EOF) { - - // Check if rising edge in modulation is detected - if (AT91C_BASE_TC1->TC_SR & AT91C_TC_LDRAS) { - - // Retrieve the new timing values - int rb = (AT91C_BASE_TC1->TC_RB / T0) + overflow; - overflow = 0; - - // Reset timer every frame, we have to capture the last edge for timing - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - - if (ledcontrol) LED_B_ON(); - // Capture reader cmd start timestamp - if (start_time == 0) { - start_time = TIMESTAMP - HITAG_T_LOW; - } - - // Capture reader frame - if (rb >= HITAG_T_STOP) { - if (rxlen != 0) { - //DbpString("weird0?"); - } - } else if (rb >= HITAG_T_1_MIN) { - // '1' bit - rx[rxlen / 8] |= 1 << (7 - (rxlen % 8)); - rxlen++; - } else if (rb >= HITAG_T_0_MIN) { - // '0' bit - rx[rxlen / 8] |= 0 << (7 - (rxlen % 8)); - rxlen++; - } else { - // Ignore weird value, is to small to mean anything - } - } - } + // Receive commands from the reader + hitag_tag_receive_frame(rx, sizeof(rx), &rxlen, &start_time, ledcontrol, &overflow); // Check if frame was captured if (rxlen > 0) { @@ -821,7 +439,7 @@ void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) { if (txlen > 0) { // Transmit the tag frame start_time = TIMESTAMP; - hitag_send_frame(tx, txlen, ledcontrol); + hitag_tag_send_frame(tx, txlen, sof_bits, m, ledcontrol); LogTraceBits(tx, txlen, start_time, TIMESTAMP, false); } @@ -842,152 +460,13 @@ void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) { } - hts_stop_clock(); - set_tracing(false); - lf_finalize(ledcontrol); + hitag_cleanup(ledcontrol); // release allocated memory from BigBuff. BigBuf_free(); DbpString("Sim stopped"); } -static void hts_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *resptime, bool ledcontrol) { - - // Reset values for receiving frames - memset(rx, 0x00, sizeofrx); - *rxlen = 0; - - int lastbit = 1; - bool bSkip = true; - uint32_t errorCount = 0; - bool bStarted = false; - uint16_t next_edge_event = AT91C_TC_LDRBS; - int double_speed = (m == AC4K || m == MC8K) ? 2 : 1; - - uint32_t rb_i = 0, h2 = 0, h3 = 0, h4 = 0; - uint8_t edges[160] = {0}; - - // Receive tag frame, watch for at most T0*HITAG_T_PROG_MAX periods - while (AT91C_BASE_TC0->TC_CV < (T0 * HITAG_T_PROG_MAX)) { - - // Check if edge in tag modulation is detected - if (AT91C_BASE_TC1->TC_SR & next_edge_event) { - - next_edge_event = next_edge_event ^ (AT91C_TC_LDRAS | AT91C_TC_LDRBS); - - // only use AT91C_TC_LDRBS falling edge for now - if (next_edge_event == AT91C_TC_LDRBS) continue; - - // Retrieve the new timing values - uint32_t rb = AT91C_BASE_TC1->TC_RB / T0; - edges[rb_i++] = rb; - // Reset timer every frame, we have to capture the last edge for timing - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - - if (ledcontrol) LED_B_ON(); - - // Capture tag response timestamp - if (*rxlen == 0) *resptime = TIMESTAMP; - - // Capture tag frame (manchester decoding using only falling edges) - if (bStarted == false) { - - if (rb >= HITAG_T_WAIT_RESP) { - bStarted = true; - - // We always receive a 'one' first, which has the falling edge after a half period |-_| - rx[0] = 0x80; - (*rxlen)++; - } else { - errorCount++; - } - - } else { - // Anticollision Coding - if (m == AC2K || m == AC4K) { - if (rb >= HITAG_T_TAG_CAPTURE_FOUR_HALF / double_speed) { - // Anticollision Coding example |--__|--__| (00) - lastbit = 0; - rx[(*rxlen) / 8] |= lastbit << (7 - ((*rxlen) % 8)); - (*rxlen)++; - } else if (rb >= HITAG_T_TAG_CAPTURE_THREE_HALF / double_speed) { - // Anticollision Coding example |-_-_|--__| (10) or |--__|-_-_| (01) - lastbit = !lastbit; - rx[(*rxlen) / 8] |= lastbit << (7 - ((*rxlen) % 8)); - (*rxlen)++; - - bSkip = !!lastbit; - } else if (rb >= HITAG_T_TAG_CAPTURE_TWO_HALF / double_speed) { - // Anticollision Coding example |-_-_| (1) - if (bSkip == false) { - lastbit = 1; - rx[(*rxlen) / 8] |= lastbit << (7 - ((*rxlen) % 8)); - (*rxlen)++; - } - - bSkip = !bSkip; - } else { - // Ignore weird value, is to small to mean anything - errorCount++; - } - } else { - // Manchester coding - if (rb >= HITAG_T_TAG_CAPTURE_FOUR_HALF / double_speed) { - // Manchester coding example |-_|_-|-_| (101) - rx[(*rxlen) / 8] |= 0 << (7 - ((*rxlen) % 8)); - (*rxlen)++; - - rx[(*rxlen) / 8] |= 1 << (7 - ((*rxlen) % 8)); - (*rxlen)++; - h4++; - } else if (rb >= HITAG_T_TAG_CAPTURE_THREE_HALF / double_speed) { - // Manchester coding example |_-|...|_-|-_| (0...01) - rx[(*rxlen) / 8] |= 0 << (7 - ((*rxlen) % 8)); - (*rxlen)++; - - // We have to skip this half period at start and add the 'one' the second time - if (bSkip == false) { - rx[(*rxlen) / 8] |= 1 << (7 - ((*rxlen) % 8)); - (*rxlen)++; - } - - lastbit = !lastbit; - bSkip = !bSkip; - h3++; - } else if (rb >= HITAG_T_TAG_CAPTURE_TWO_HALF / double_speed) { - // Manchester coding example |_-|_-| (00) or |-_|-_| (11) - // bit is same as last bit - rx[(*rxlen) / 8] |= lastbit << (7 - ((*rxlen) % 8)); - (*rxlen)++; - h2++; - } else { - // Ignore weird value, is to small to mean anything - errorCount++; - } - } - } - } - - // if we saw over 100 weird values break it probably isn't hitag... - if (errorCount > 100 || (*rxlen) / 8 >= sizeofrx) { - break; - } - - // We can break this loop if we received the last bit from a frame - // max periods between 2 falling edge - // RTF AC64 |--__|--__| (00) 64 * T0 - // RTF MC32 |_-|-_|_-| (010) 48 * T0 - if (AT91C_BASE_TC1->TC_CV > (T0 * 80)) { - if ((*rxlen)) { - break; - } - } - } - - DBG Dbprintf("RX0 %i:%02X.. err:%i resptime:%i h2:%i h3:%i h4:%i edges:", *rxlen, rx[0], errorCount, *resptime, h2, h3, h4); - DBG Dbhexdump(rb_i, edges, false); -} - static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t sizeofrx, size_t *rxlen, int t_wait, bool ledcontrol, bool ac_seq) { uint32_t start_time; @@ -1005,7 +484,7 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t start_time = TIMESTAMP; // Transmit the reader frame - hitag_reader_send_frame(tx, txlen, ledcontrol); + hitag_reader_send_frame(tx, txlen, ledcontrol, false); if (enable_page_tearoff && tearoff_hook() == PM3_ETEAROFF) { return PM3_ETEAROFF; @@ -1018,7 +497,8 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t hts_set_frame_modulation(protocol_mode, ac_seq); - hts_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol); + hitag_reader_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol, m, sof_bits); + // hts_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol); // Check if frame was captured and store it if (*rxlen > 0) { @@ -1033,23 +513,8 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t Dbprintf("htS: sizeofrx... %zu", sizeofrx); DbpString("htS: response_bit:"); Dbhexdump(*rxlen, response_bit, false); - Dbprintf("htS: skipping %d bit SOF", sof_bits); - - if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { - DBG DbpString("htS: Warning, not all bits of SOF are 1"); - } } - // remove first sof_bits bits SOF - for (size_t i = 0; i < (*rxlen + 8) / 8; i++) { - rx[i] <<= sof_bits; - if (i + 1 < (*rxlen + 8) / 8) { - rx[i] |= (rx[i + 1] >> (8 - sof_bits)); - } - } - - *rxlen -= sof_bits; - LogTraceBits(rx, *rxlen, start_time, TIMESTAMP, false); } @@ -1057,37 +522,15 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t } static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t sizeoftx, uint8_t *rx, size_t sizeofrx, int t_wait, bool ledcontrol) { + size_t txlen = 0; + size_t rxlen = 0; - StopTicks(); - - FpgaDownloadAndGo(FPGA_BITSTREAM_LF); - - // Clean up trace and prepare it for storing frames - set_tracing(true); - clear_trace(); - - if (ledcontrol) LED_D_ON(); - - hts_init_clock(); - - // Set fpga in edge detect with reader field, we can modulate as reader now - FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | FPGA_LF_EDGE_DETECT_READER_FIELD); - FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz - SetAdcMuxFor(GPIO_MUXSEL_LOPKD); - - // Configure output and enable pin that is connected to the FPGA (for modulating) - AT91C_BASE_PIOA->PIO_OER |= GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER |= GPIO_SSC_DOUT; - - // Disable modulation at default, which means enable the field - LOW(GPIO_SSC_DOUT); + // Setup FPGA and initialize + hitag_setup_fpga(FPGA_LF_EDGE_DETECT_READER_FIELD, 127, ledcontrol); // UID request standard 00110 // UID request Advanced 1100x // UID request FAdvanced 11010 - size_t txlen = 0; - size_t rxlen = 0; - protocol_mode = packet->mode; uint8_t cmd = protocol_mode; txlen = concatbits(tx, txlen, &cmd, 0, 5, false); @@ -1356,9 +799,7 @@ void hts_read(const lf_hitag_data_t *payload, bool ledcontrol) { } read_end: - hts_stop_clock(); - set_tracing(false); - lf_finalize(ledcontrol); + hitag_cleanup(ledcontrol); reply_reason(CMD_LF_HITAGS_READ, status, reason, (uint8_t *)&card, sizeof(card)); } @@ -1449,41 +890,14 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { } write_end: - hts_stop_clock(); - set_tracing(false); - lf_finalize(ledcontrol); + hitag_cleanup(ledcontrol); reply_reason(CMD_LF_HITAGS_WRITE, status, reason, NULL, 0); } int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) { + // Setup FPGA and initialize + hitag_setup_fpga(FPGA_LF_EDGE_DETECT_READER_FIELD, 127, ledcontrol); - StopTicks(); - - FpgaDownloadAndGo(FPGA_BITSTREAM_LF); - - // Clean up trace and prepare it for storing frames - set_tracing(true); - clear_trace(); - - if (ledcontrol) LED_D_ON(); - - hts_init_clock(); - - // Set fpga in edge detect with reader field, we can modulate as reader now - FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | FPGA_LF_EDGE_DETECT_READER_FIELD); - FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz - SetAdcMuxFor(GPIO_MUXSEL_LOPKD); - - // Configure output and enable pin that is connected to the FPGA (for modulating) - AT91C_BASE_PIOA->PIO_OER |= GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER |= GPIO_SSC_DOUT; - - // Disable modulation at default, which means enable the field - LOW(GPIO_SSC_DOUT); - - // UID request standard 00110 - // UID request Advanced 1100x - // UID request FAdvanced 11010 protocol_mode = HITAGS_UID_REQ_ADV1; uint8_t cmd = protocol_mode; @@ -1511,10 +925,10 @@ int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) { status = PM3_ERFTRANS; } - hts_stop_clock(); - set_tracing(false); - lf_finalize(ledcontrol); - reply_ng(CMD_LF_HITAGS_UID, status, (uint8_t *)tag.data.pages, sizeof(tag.data.pages)); + hitag_cleanup(ledcontrol); + if (send_answer) { + reply_ng(CMD_LF_HITAGS_UID, status, (uint8_t *)tag.data.pages, sizeof(tag.data.pages)); + } return status; } @@ -1576,9 +990,7 @@ void hts_check_challenges(const uint8_t *data, uint32_t datalen, bool ledcontrol SpinDelay(2); } - hts_stop_clock(); - set_tracing(false); - lf_finalize(ledcontrol); + hitag_cleanup(ledcontrol); reply_ng(CMD_LF_HITAGS_TEST_TRACES, PM3_SUCCESS, NULL, 0); return; } diff --git a/armsrc/hitagS.h b/armsrc/hitagS.h index 8c49439dc..5c2008b31 100644 --- a/armsrc/hitagS.h +++ b/armsrc/hitagS.h @@ -24,7 +24,7 @@ #include "common.h" #include "hitag.h" -void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol); +void hts_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol); void hts_read(const lf_hitag_data_t *payload, bool ledcontrol); void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol); void hts_check_challenges(const uint8_t *data, uint32_t datalen, bool ledcontrol); diff --git a/armsrc/hitag_common.c b/armsrc/hitag_common.c new file mode 100644 index 000000000..43837486f --- /dev/null +++ b/armsrc/hitag_common.c @@ -0,0 +1,539 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Hitag shared functionality +//----------------------------------------------------------------------------- + +#include "hitag_common.h" + +#include "proxmark3_arm.h" +#include "cmd.h" +#include "BigBuf.h" +#include "fpgaloader.h" +#include "ticks.h" +#include "dbprint.h" +#include "util.h" +#include "string.h" +#include "commonutil.h" +#include "hitag2/hitag2_crypto.h" +#include "lfadc.h" +#include "crc.h" +#include "protocols.h" +#include "appmain.h" // tearoff_hook() + +uint16_t timestamp_high = 0; // Timer Counter 2 overflow count, combined with TC2 counter for ~47min timing + +static void hitag_stop_clock(void) { + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKDIS; +} + +static void hitag_init_clock(void) { + // Enable Peripheral Clock for + // Timer Counter 0, used to measure exact timing before answering + // Timer Counter 1, used to capture edges of the tag frames + // Timer Counter 2, used to log trace time + AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1) | (1 << AT91C_ID_TC2); + + AT91C_BASE_PIOA->PIO_BSR = GPIO_SSC_FRAME; + + // Disable timer during configuration + hitag_stop_clock(); + + // TC0: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers + AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; + + // TC1: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), TIOA is external trigger, + AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK // use MCK/32 (TIMER_CLOCK3) + | AT91C_TC_ABETRG // TIOA is used as an external trigger + | AT91C_TC_ETRGEDG_FALLING // external trigger on falling edge + | AT91C_TC_LDRA_RISING // load RA on on rising edge of TIOA + | AT91C_TC_LDRB_FALLING; // load RB on on falling edge of TIOA + + // TC2: Capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers + AT91C_BASE_TC2->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; + + // Enable and reset counters + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + + // Assert a sync signal. This sets all timers to 0 on next active clock edge + AT91C_BASE_TCB->TCB_BCR = 1; + + // synchronized startup procedure + // In theory, with MCK/32, we shouldn't be waiting longer than 32 instruction statements, right? + while (AT91C_BASE_TC0->TC_CV != 0) { + }; // wait until TC0 returned to zero + + // reset timestamp + timestamp_high = 0; +} + +// Initialize FPGA and timer for Hitag operations +void hitag_setup_fpga(uint16_t conf, uint8_t threshold, bool ledcontrol) { + StopTicks(); + + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + + // Clean up trace and prepare it for storing frames + set_tracing(true); + clear_trace(); + + if (ledcontrol) LED_D_ON(); + + hitag_init_clock(); + + // Set fpga in edge detect with/without reader field, we can modulate as reader/tag now + FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | conf); + FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz + if (threshold != 127) FpgaSendCommand(FPGA_CMD_SET_EDGE_DETECT_THRESHOLD, threshold); + SetAdcMuxFor(GPIO_MUXSEL_LOPKD); + + // Configure output and enable pin that is connected to the FPGA (for modulating) + AT91C_BASE_PIOA->PIO_OER |= GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_PER |= GPIO_SSC_DOUT; + + // Disable modulation at default, which means enable the field + LOW(GPIO_SSC_DOUT); +} + +// Clean up and finalize Hitag operations +void hitag_cleanup(bool ledcontrol) { + hitag_stop_clock(); + set_tracing(false); + lf_finalize(ledcontrol); +} + +// Reader functions +static void hitag_reader_send_bit(int bit, bool ledcontrol) { + // Reset clock for the next bit + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (AT91C_BASE_TC0->TC_CV != 0) {}; + + if (ledcontrol) LED_A_ON(); + + // Binary puls length modulation (BPLM) is used to encode the data stream + // This means that a transmission of a one takes longer than that of a zero + HIGH(GPIO_SSC_DOUT); + + // Wait for 4-10 times the carrier period + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_LOW) {}; + + LOW(GPIO_SSC_DOUT); + + if (bit == 0) { + // Zero bit: |_-| + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_0) {}; + } else { + // One bit: |_--| + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_1) {}; + } + + if (ledcontrol) LED_A_OFF(); +} + +void hitag_reader_send_frame(const uint8_t *frame, size_t frame_len, bool ledcontrol, bool send_sof) { + // Send SOF (Start of Frame) for Hitag µ if requested + if (send_sof) { + hitag_reader_send_bit(0, ledcontrol); + + // Reset clock for the code violation + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (AT91C_BASE_TC0->TC_CV != 0) {}; + + if (ledcontrol) LED_A_ON(); + + // SOF is HIGH for HITAG_T_LOW + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_LOW) {}; + + // Then LOW for HITAG_T_CODE_VIOLATION + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_CODE_VIOLATION) {}; + + if (ledcontrol) LED_A_OFF(); + } + + // Send the content of the frame + for (size_t i = 0; i < frame_len; i++) { + hitag_reader_send_bit(TEST_BIT_MSB(frame, i), ledcontrol); + } + + // Send EOF + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (AT91C_BASE_TC0->TC_CV != 0) {}; + + HIGH(GPIO_SSC_DOUT); + + // Wait for 4-10 times the carrier period + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_LOW) {}; + + LOW(GPIO_SSC_DOUT); +} + +void hitag_reader_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *resptime, bool ledcontrol, + MOD modulation, int sof_bits) { + // Reset values for receiving frames + memset(rx, 0x00, sizeofrx); + *rxlen = 0; + + int lastbit = 1; + bool bSkip = true; + uint32_t errorCount = 0; + bool bStarted = false; + uint16_t next_edge_event = AT91C_TC_LDRBS; + int double_speed = (modulation == AC4K || modulation == MC8K) ? 2 : 1; + + uint32_t rb_i = 0; + uint8_t edges[160] = {0}; + + // Skip SOF bits + bool sof_received = false; + + // Receive tag frame, watch for at most T0*HITAG_T_PROG_MAX periods + while (AT91C_BASE_TC0->TC_CV < (T0 * HITAG_T_PROG_MAX)) { + // Check if edge in tag modulation is detected + if (AT91C_BASE_TC1->TC_SR & next_edge_event) { + next_edge_event = next_edge_event ^ (AT91C_TC_LDRAS | AT91C_TC_LDRBS); + + // only use AT91C_TC_LDRBS falling edge for now + if (next_edge_event == AT91C_TC_LDRBS) continue; + + // Retrieve the new timing values + uint32_t rb = AT91C_BASE_TC1->TC_RB / T0; + edges[rb_i++] = rb; + + // Reset timer every frame, we have to capture the last edge for timing + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + + if (ledcontrol) LED_B_INV(); + + // Capture tag frame (manchester decoding using only falling edges) + if (bStarted == false) { + if (rb >= HITAG_T_WAIT_RESP) { + bStarted = true; + + // Capture tag response timestamp + *resptime = TIMESTAMP; + + // We always receive a 'one' first, which has the falling edge after a half period |-_| + rx[0] = 0x80; + *rxlen = 1; + } else { + errorCount++; + } + } else { + // Handle different modulation types + if (modulation == AC2K || modulation == AC4K) { + // Anticollision Coding + if (rb >= HITAG_T_TAG_CAPTURE_FOUR_HALF / double_speed) { + // Anticollision Coding example |--__|--__| (00) + lastbit = 0; + // CLEAR_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } else if (rb >= HITAG_T_TAG_CAPTURE_THREE_HALF / double_speed) { + // Anticollision Coding example |-_-_|--__| (10) or |--__|-_-_| (01) + lastbit = !lastbit; + if (lastbit) SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + + bSkip = !!lastbit; + } else if (rb >= HITAG_T_TAG_CAPTURE_TWO_HALF / double_speed) { + // Anticollision Coding example |-_-_| (1) + if (bSkip == false) { + lastbit = 1; + SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } + + bSkip = !bSkip; + } else { + // Ignore weird value, is to small to mean anything + errorCount++; + } + } else { + // Manchester coding (MC4K, MC8K) + if (rb >= HITAG_T_TAG_CAPTURE_FOUR_HALF / double_speed) { + // Manchester coding example |-_|_-|-_| (101) + // CLEAR_BIT_MSB(rx, *rxlen); + (*rxlen)++; + + SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } else if (rb >= HITAG_T_TAG_CAPTURE_THREE_HALF / double_speed) { + // Manchester coding example |_-|...|_-|-_| (0...01) + // CLEAR_BIT_MSB(rx, *rxlen); + (*rxlen)++; + + // We have to skip this half period at start and add the 'one' the second time + if (bSkip == false) { + SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } + + lastbit = !lastbit; + bSkip = !bSkip; + } else if (rb >= HITAG_T_TAG_CAPTURE_TWO_HALF / double_speed) { + // Manchester coding example |_-|_-| (00) or |-_|-_| (11) + // bit is same as last bit + if (lastbit) SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } else { + // Ignore weird value, is to small to mean anything + errorCount++; + } + } + + // Handle SOF bits + if (sof_received == false && *rxlen >= sof_bits) { + // Check if SOF is valid (all bits should be 1) + if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { + if (sof_bits == 4) { + sof_bits = 3; + // Hitag µ is LSB first 0b110 + if ((rx[0] & 0xE0) != 0xC0) { + DBG Dbprintf("Warning, SOF is invalid rx[0]: 0x%02X", rx[0]); + } + } else { + DBG DbpString("Warning, not all bits of SOF are 1"); + } + } + + *rxlen -= sof_bits; + uint8_t tmp = rx[0]; + rx[0] = 0x00; + for (size_t i = 0; i < *rxlen; i++) { + if (TEST_BIT_MSB(&tmp, sof_bits + i)) SET_BIT_MSB(rx, i); + } + // DBG Dbprintf("after sof_bits rxlen: %d rx[0]: 0x%02X", *rxlen, rx[0]); + sof_received = true; + } + } + } + + // if we saw over 100 weird values break it probably isn't hitag... + if (errorCount > 100 || (*rxlen) / 8 >= sizeofrx) { + break; + } + + // We can break this loop if we received the last bit from a frame + // max periods between 2 falling edge + // RTF AC64 |--__|--__| (00) 64 * T0 + // RTF MC32 |_-|-_|_-| (010) 48 * T0 + if (AT91C_BASE_TC1->TC_CV > (T0 * 80)) { + if (bStarted) { + break; + } + } + } + + DBG { + Dbprintf("RX %i:%02X.. resptime:%i edges:", *rxlen, rx[0], *resptime); + Dbhexdump(rb_i, edges, false); + } +} + +// Tag functions - depends on modulation type +static void hitag_tag_send_bit(int bit, MOD modulation, bool ledcontrol) { + // Reset clock for the next bit + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + + if (ledcontrol) LED_A_ON(); + + switch (modulation) { + case AC2K: { + if (bit == 0) { + // AC Coding --__ + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 64) {}; + } else { + // AC coding -_-_ + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; + + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 48) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 64) {}; + } + break; + } + case AC4K: { + if (bit == 0) { + // AC Coding --__ + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_TAG_HALF_PERIOD) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * HITAG_T_TAG_FULL_PERIOD) {}; + } else { + // AC coding -_-_ + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 24) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; + } + break; + } + case MC4K: { + if (bit == 0) { + // Manchester: Unloaded, then loaded |__--| + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; + } else { + // Manchester: Loaded, then unloaded |--__| + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 32) {}; + } + break; + } + case MC8K: { + if (bit == 0) { + // Manchester: Unloaded, then loaded |__--| + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; + + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + } else { + // Manchester: Loaded, then unloaded |--__| + HIGH(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 8) {}; + + LOW(GPIO_SSC_DOUT); + while (AT91C_BASE_TC0->TC_CV < T0 * 16) {}; + } + break; + } + } + + if (ledcontrol) LED_A_OFF(); +} + +void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow) { + uint16_t next_edge_event = AT91C_TC_LDRBS; + uint8_t edges[160] = {0}; + uint32_t rb_i = 0; + + // Receive frame, watch for at most T0*EOF periods + while (AT91C_BASE_TC1->TC_CV < T0 * HITAG_T_EOF) { + + // Check if edge in modulation is detected + if (AT91C_BASE_TC1->TC_SR & next_edge_event) { + next_edge_event = next_edge_event ^ (AT91C_TC_LDRAS | AT91C_TC_LDRBS); + + // only use AT91C_TC_LDRBS falling edge for now + if (next_edge_event == AT91C_TC_LDRBS) continue; + + // Retrieve the new timing values + uint32_t rb = AT91C_BASE_TC1->TC_RB / T0 + *overflow; + *overflow = 0; + + edges[rb_i++] = rb; + + if (ledcontrol) LED_B_INV(); + + // Capture reader cmd start timestamp + if (*start_time == 0) { + *start_time = TIMESTAMP - HITAG_T_LOW; + } + + // Capture reader frame + if (rb >= HITAG_T_STOP) { + // Hitag µ SOF + if (*rxlen != 0 && *rxlen != 1) { + // DBG DbpString("weird0?"); + break; + } + *rxlen = 0; + } else if (rb >= HITAG_T_1_MIN) { + // '1' bit + SET_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } else if (rb >= HITAG_T_0_MIN) { + // '0' bit + // CLEAR_BIT_MSB(rx, *rxlen); + (*rxlen)++; + } else { + // Ignore weird value, is too small to mean anything + } + } + } + + if (ledcontrol) LED_B_OFF(); + + DBG if (rb_i) { + Dbprintf("RX %i bits.. start_time:%i edges:", *rxlen, *start_time); + Dbhexdump(rb_i, edges, false); + } +} + +void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits, MOD modulation, bool ledcontrol) { + // The beginning of the frame is hidden in some high level; pause until our bits will have an effect + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + HIGH(GPIO_SSC_DOUT); + + switch (modulation) { + case AC4K: + case MC8K: { + while (AT91C_BASE_TC0->TC_CV < T0 * 40) {}; // FADV + break; + } + case AC2K: + case MC4K: { + while (AT91C_BASE_TC0->TC_CV < T0 * 20) {}; // STD + ADV + break; + } + } + + // SOF - send start of frame + for (size_t i = 0; i < sof_bits; i++) { + if (sof_bits == 4 && i == 3) { + // Hitag µ SOF is 110 + hitag_tag_send_bit(0, modulation, ledcontrol); + break; + } else + hitag_tag_send_bit(1, modulation, ledcontrol); + } + + // Send the content of the frame + for (size_t i = 0; i < frame_len; i++) { + hitag_tag_send_bit(TEST_BIT_MSB(frame, i), modulation, ledcontrol); + } + + LOW(GPIO_SSC_DOUT); +} diff --git a/armsrc/hitag_common.h b/armsrc/hitag_common.h new file mode 100644 index 000000000..16a966240 --- /dev/null +++ b/armsrc/hitag_common.h @@ -0,0 +1,57 @@ +#ifndef HITAG_COMMON_H +#define HITAG_COMMON_H + +#include "hitag.h" + +// Sam7s has several timers, we will use the source TIMER_CLOCK3 (aka AT91C_TC_CLKS_TIMER_DIV3_CLOCK) +// TIMER_CLOCK3 = MCK/32, MCK is running at 48 MHz, Timer is running at 48MHz/32 = 1500 KHz +// Hitag units (T0) have duration of 8 microseconds (us), which is 1/125000 per second (carrier) +// T0 = TIMER_CLOCK3 / 125000 = 12 + +#define T0 12 + +#define HITAG_FRAME_LEN 20 + +// TC0 and TC1 are 16-bit counters and will overflow after 5461 * T0 +// Ensure not to set these timings above 5461 (~43ms) when comparing without considering overflow, as they will never reach that value. + +#define HITAG_T_LOW 8 /* T_LOW should be 4..10 */ +#define HITAG_T_0 20 /* T[0] should be 18..22 */ +#define HITAG_T_1 28 /* T[1] should be 26..32 */ +#define HITAG_T_0_MIN 15 /* T[0] should be 18..22 */ +#define HITAG_T_1_MIN 25 /* T[1] should be 26..32 */ +#define HITAG_T_STOP 36 /* T_EOF should be > 36 */ +#define HITAG_T_CODE_VIOLATION 36 /* Hitag µ TFcv should be 34..38 */ +#define HITAG_T_EOF 80 /* T_EOF should be > 36 */ + +#define HITAG_T_WAIT_RESP 200 /* T_wresp should be 204..212 */ +#define HITAG_T_WAIT_SC 200 /* T_wsc should be 90..5000 */ +#define HITAG_T_WAIT_FIRST 300 /* T_wfc should be 280..565 (T_ttf) */ +#define HITAG_T_PROG_MAX 750 /* T_prog should be 716..726 */ + +#define HITAG_T_TAG_ONE_HALF_PERIOD 10 +#define HITAG_T_TAG_TWO_HALF_PERIOD 25 +#define HITAG_T_TAG_THREE_HALF_PERIOD 41 +#define HITAG_T_TAG_FOUR_HALF_PERIOD 57 + +#define HITAG_T_TAG_HALF_PERIOD 16 +#define HITAG_T_TAG_FULL_PERIOD 32 + +#define HITAG_T_TAG_CAPTURE_ONE_HALF 13 +#define HITAG_T_TAG_CAPTURE_TWO_HALF 25 +#define HITAG_T_TAG_CAPTURE_THREE_HALF 41 +#define HITAG_T_TAG_CAPTURE_FOUR_HALF 57 + +extern uint16_t timestamp_high; +#define TIMESTAMP ( (AT91C_BASE_TC2->TC_SR & AT91C_TC_COVFS) ? timestamp_high += 1 : 0, ((timestamp_high << 16) + AT91C_BASE_TC2->TC_CV) / T0) + +// Common hitag functions +void hitag_setup_fpga(uint16_t conf, uint8_t threshold, bool ledcontrol); +void hitag_cleanup(bool ledcontrol); +void hitag_reader_send_frame(const uint8_t *frame, size_t frame_len, bool ledcontrol, bool send_sof); +void hitag_reader_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *resptime, bool ledcontrol, MOD modulation, + int sof_bits); +void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow); +void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits, MOD modulation, bool ledcontrol); + +#endif diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index ec9e7361a..2cafdb161 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -793,9 +793,6 @@ void annotateHitag2(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, free(binstr); } -void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { -} - static const char *identify_transponder_hitag2(uint32_t uid) { switch (uid) { diff --git a/client/src/cmdlfhitag.h b/client/src/cmdlfhitag.h index 81bc26a7e..5c82613cf 100644 --- a/client/src/cmdlfhitag.h +++ b/client/src/cmdlfhitag.h @@ -31,7 +31,6 @@ int ht2_read_uid(void); int ht2_read_paxton(void); 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, uint8_t bits, bool is_response, const uint64_t *keys, uint32_t keycount, bool isdecrypted); -void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response); void annotateHitag2_init(void); bool hitag2_get_plain(uint8_t *plain, uint8_t *plen); diff --git a/client/src/cmdlfhitaghts.c b/client/src/cmdlfhitaghts.c index e14c870ad..fdf8fbe28 100644 --- a/client/src/cmdlfhitaghts.c +++ b/client/src/cmdlfhitaghts.c @@ -36,6 +36,8 @@ static int CmdHelp(const char *Cmd); +void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) {} + static const char *hts_get_type_str(uint32_t uid) { // source 1: https://www.scorpio-lk.com/downloads/Tango/HITAG_Classification.pdf // IDE Mark @@ -88,7 +90,7 @@ static bool hts_get_uid(uint32_t *uid) { } if (resp.status != PM3_SUCCESS) { - PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting UID"); + PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag S UID"); return false; } @@ -253,10 +255,38 @@ static void print_error(int8_t reason) { break; default: // PM3_REASON_UNKNOWN - PrintAndLogEx(DEBUG, "DEBUG: Error - Hitag S failed"); + PrintAndLogEx(FAILED, "Error - Hitag S failed"); } } +static void hitags_config_print(hitags_config_t config) { + PrintAndLogEx(INFO, " Memory type...... " _GREEN_("%s"), + (const char *[]) {"Hitag S 32", "Hitag S 256", "Hitag S 2048", "Unknown Hitag S/8211"}[config.MEMT]); + + PrintAndLogEx(INFO, " Authenticaion.... %s", config.auth ? _YELLOW_("Yes") : "No"); + + PrintAndLogEx(INFO, " TTF coding....... %s", + config.RES3 ? "FSK 0=RF/10 1=RF/8" : (const char *[]) {"Manchester", "Biphase"}[config.TTFC]); + + PrintAndLogEx(INFO, " TTF data rate.... %s", + (const char *[]) {"4 kBit", "8 kBit", "2 kBit", "2 kBit and Pigeon Race Standard"}[config.TTFDR]); + + PrintAndLogEx(INFO, " TTF mode......... %s", + (const char *[]) { + "TTF Mode disabled (= RTF Mode)", + "Page 4, Page 5", + "Page 4, Page 5, Page 6, Page 7", + "Page 4", + "TTF Mode disabled (= RTF Mode)", + "Page 4, Page 5, Page 6", + "Page 4, Page 5, Page 6, Page 7, Page 8", + "Page 4, Page 5, Page 6, Page 7, Page 8, Page 9, Page 10, Page 11", + }[config.RES0 << 2 | config.TTFM]); + + PrintAndLogEx(INFO, " Config locked.... %s", config.LCON ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Key/PWD locked... %s", config.LKP ? _RED_("Yes") : _GREEN_("No")); +} + static int CmdLFHitagSRead(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf hitag hts rdbl", @@ -868,20 +898,23 @@ static int CmdLFHitagSSim(const char *Cmd) { "Simulate Hitag S transponder\n" "You need to `lf hitag hts eload` first", "lf hitag hts sim\n" - "lf hitag hts sim --82xx"); + "lf hitag hts sim --82xx\n" + "lf hitag hts sim -t 30 -> set threshold to 30"); void *argtable[] = { arg_param_begin, arg_lit0("8", "82xx", "simulate 8268/8310"), + arg_int0("t", "threshold", "", "set edge detect threshold (def: 127)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); // bool use_82xx = arg_get_lit(ctx, 1); // not implemented yet + int threshold = arg_get_int_def(ctx, 2, 127); CLIParserFree(ctx); clearCommandBuffer(); - SendCommandMIX(CMD_LF_HITAGS_SIMULATE, false, 0, 0, NULL, 0); + SendCommandMIX(CMD_LF_HITAGS_SIMULATE, false, threshold, 0, NULL, 0); return PM3_SUCCESS; } @@ -889,40 +922,6 @@ static int CmdLFHitagSList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag hts", "hitags"); } -void hitags_config_print(hitags_config_t config) { - PrintAndLogEx(INFO, " Memory type...... " _GREEN_("%s"), - (const char *[]) { - "Hitag S 32", "Hitag S 256", "Hitag S 2048", - "Unknown Hitag S/8211" - }[config.MEMT]); - - PrintAndLogEx(INFO, " Authenticaion.... %s", config.auth ? _YELLOW_("Yes") : "No"); - - PrintAndLogEx(INFO, " TTF coding....... %s", - config.RES3 ? "FSK 0=RF/10 1=RF/8" : (const char *[]) {"Manchester", "Biphase"}[config.TTFC]); - - PrintAndLogEx(INFO, " TTF data rate.... %s", - (const char *[]) { - "4 kBit", "8 kBit", "2 kBit", - "2 kBit and Pigeon Race Standard" - }[config.TTFDR]); - - PrintAndLogEx(INFO, " TTF mode......... %s", - (const char *[]) { - "TTF Mode disabled (= RTF Mode)", - "Page 4, Page 5", - "Page 4, Page 5, Page 6, Page 7", - "Page 4", - "TTF Mode disabled (= RTF Mode)", - "Page 4, Page 5, Page 6", - "Page 4, Page 5, Page 6, Page 7, Page 8", - "Page 4, Page 5, Page 6, Page 7, Page 8, Page 9, Page 10, Page 11", - }[config.RES0 << 2 | config.TTFM]); - - PrintAndLogEx(INFO, " Config locked.... %s", config.LCON ? _RED_("Yes") : _GREEN_("No")); - PrintAndLogEx(INFO, " Key/PWD locked... %s", config.LKP ? _RED_("Yes") : _GREEN_("No")); -} - static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdLFHitagSList, AlwaysAvailable, "List Hitag S trace history"}, diff --git a/client/src/cmdlfhitaghts.h b/client/src/cmdlfhitaghts.h index 171510d7c..d22fa4518 100644 --- a/client/src/cmdlfhitaghts.h +++ b/client/src/cmdlfhitaghts.h @@ -22,9 +22,10 @@ #include "common.h" #include "hitag.h" +void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response); + int CmdLFHitagS(const char *Cmd); int read_hts_uid(void); -void hitags_config_print(hitags_config_t config); #endif //CMDLFHITAGS_H__ diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 07ffc3377..4ae143b1c 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -28,6 +28,7 @@ #include "comms.h" // for sending cmds to device. GetFromBigBuf #include "fileutils.h" // for saveFile #include "cmdlfhitag.h" // annotate hitag +#include "cmdlfhitaghts.h" // annotate hitags #include "pm3_cmd.h" // tracelog_hdr_t #include "cliparser.h" // args.. diff --git a/include/hitag.h b/include/hitag.h index e63b8c448..330ce8ddf 100644 --- a/include/hitag.h +++ b/include/hitag.h @@ -41,11 +41,18 @@ // need to see which limits these cards has #define HITAG1_MAX_BYTE_SIZE 64 -#define HITAGU_MAX_BYTE_SIZE 64 -#define HITAG_MAX_BYTE_SIZE (64 * HITAG_BLOCK_SIZE) +#define HITAG_MAX_BYTE_SIZE (64 * HITAG_BLOCK_SIZE) #define HITAG2_CONFIG_BLOCK 3 +// Modulation types - used by shared code +typedef enum modulation { + AC2K = 0, // Amplitude modulation 2000 bits/s + AC4K, // Amplitude modulation 4000 bits/s + MC4K, // Manchester modulation 4000 bits/s + MC8K // Manchester modulation 8000 bits/s +} MOD; + typedef enum { HTSF_PLAIN, HTSF_82xx, @@ -125,7 +132,7 @@ struct hitagS_tag { int max_page; union { - uint8_t pages[64][4]; + uint8_t pages[HITAGS_MAX_PAGES][HITAGS_PAGE_SIZE]; struct { // page 0 uint32_t uid_le; @@ -147,7 +154,7 @@ typedef struct { hitag_function cmd; uint8_t page; uint8_t page_count; - uint8_t data[HITAGS_PAGE_SIZE]; + uint8_t data[HITAG_BLOCK_SIZE]; uint8_t NrAr[HITAG_NRAR_SIZE]; // unaligned access to key as uint64_t will abort. // todo: Why does the compiler without -munaligned-access generate unaligned-access code in the first place? From 2aa58457e38b638a8dcef9d1a93dbadd5f579d2a Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Wed, 19 Mar 2025 02:34:38 -0700 Subject: [PATCH 21/43] Missing lines from my last commit --- armsrc/em4x70.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 180cc9c61..72eb2702c 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -946,7 +946,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_um2(em4x70_command_bitstream_ return true; } static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *rnd, const uint8_t *frnd) { - const uint8_t expected_bits_to_send = 96u; + const uint8_t expected_bits_to_send = 95u; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); @@ -996,7 +996,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_auth(em4x70_command_bitstream return result; } static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, const uint8_t *tag_id, const uint32_t pin) { - const uint8_t expected_bits_to_send = 69; // normally 68 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit + const uint8_t expected_bits_to_send = 68; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); @@ -1030,7 +1030,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_pin(em4x70_command_bitstream_ return result; } static bool create_legacy_em4x70_bitstream_for_cmd_write(em4x70_command_bitstream_t *out_cmd_bitstream, bool with_command_parity, uint16_t new_data, uint8_t address) { - const uint8_t expected_bits_to_send = 35u; // normally 34 bits, but legacy hack inserts an extra RM bit, and always adds a command parity bit + const uint8_t expected_bits_to_send = 34u; bool result = true; memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_WRITE; From 64a4f6cd8126fdc6fce95fc4e8addca05c3fa0f3 Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Mon, 17 Mar 2025 02:02:58 +0800 Subject: [PATCH 22/43] Enhance Hitag S annotation and debugging --- armsrc/hitagS.c | 6 +++ client/src/cmdlfhitaghts.c | 78 +++++++++++++++++++++++++++++++++++++- client/src/cmdlfhitaghts.h | 2 +- client/src/cmdtrace.c | 2 +- 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 6a9f71969..4279962d6 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -474,6 +474,9 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t // Disable timer 1 with external trigger to avoid triggers during our own modulation AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + DBG Dbprintf("tx %d bits:", txlen); + DBG Dbhexdump((txlen + 7) / 8, tx, false); + // Wait for HITAG_T_WAIT_SC carrier periods after the last tag bit before transmitting, // Since the clock counts since the last falling edge, a 'one' means that the // falling edge occurred halfway the period. with respect to this falling edge, @@ -500,6 +503,9 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t hitag_reader_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol, m, sof_bits); // hts_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol); + DBG Dbprintf("rx %d bits:", *rxlen); + DBG Dbhexdump((*rxlen + 7) / 8, rx, false); + // Check if frame was captured and store it if (*rxlen > 0) { DBG { diff --git a/client/src/cmdlfhitaghts.c b/client/src/cmdlfhitaghts.c index fdf8fbe28..13846efc9 100644 --- a/client/src/cmdlfhitaghts.c +++ b/client/src/cmdlfhitaghts.c @@ -36,7 +36,83 @@ static int CmdHelp(const char *Cmd); -void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) {} +void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t nbits, bool is_response) { + size_t exp_len = 0; + uint8_t command = 0; + + if (is_response) { + // Handle responses + if (nbits == 32) { + exp_len = snprintf(exp, size, "UID: [%02X%02X%02X%02X]", cmd[0], cmd[1], cmd[2], cmd[3]); + } else if (nbits == 40) { + exp_len = snprintf(exp, size, "Data"); + } + } else if (nbits >= 5) { + concatbits(&command, 0, cmd, 0, 5, false); + + if (nbits == 5) { + concatbits(&command, 0, cmd, 0, 5, false); + + switch (command) { + case HITAGS_UID_REQ_STD: + exp_len += snprintf(exp + exp_len, size - exp_len, "UID Request (Standard 00110)"); + break; + case HITAGS_UID_REQ_ADV1: + exp_len += snprintf(exp + exp_len, size - exp_len, "UID Request (Advanced 11000)"); + break; + case HITAGS_UID_REQ_ADV2: + exp_len += snprintf(exp + exp_len, size - exp_len, "UID Request (Advanced 11001)"); + break; + case HITAGS_UID_REQ_FADV: + exp_len += snprintf(exp + exp_len, size - exp_len, "UID Request (Fast Advanced 11010)"); + break; + } + } else if (nbits == 4 + 8 + 8) { + concatbits(&command, 0, cmd, 0, 4, false); + + if (command == HITAGS_READ_PAGE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "READ"); + } else if (command == HITAGS_WRITE_PAGE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE"); + } else if (command == HITAGS_READ_BLOCK) { + exp_len += snprintf(exp + exp_len, size - exp_len, "READ_BLOCK"); + } else if (command == HITAGS_WRITE_BLOCK) { + exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE_BLOCK"); + } else if (command == HITAGS_QUIET) { + exp_len += snprintf(exp + exp_len, size - exp_len, "QUIET"); + } + // Hitag 1 commands + else if (command == HITAG1_RDCPAGE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "RDCPAGE"); + } else if (command == HITAG1_RDCBLK) { + exp_len += snprintf(exp + exp_len, size - exp_len, "RDCBLK"); + } else if (command == HITAG1_WRCPAGE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "WRCPAGE"); + } else if (command == HITAG1_WRCBLK) { + exp_len += snprintf(exp + exp_len, size - exp_len, "WRCBLK"); + } else { + exp_len += snprintf(exp + exp_len, size - exp_len, "Unknown (%02X)", command); + } + + uint8_t page = 0; + concatbits(&page, 0, cmd, 5, 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, " Page: %d", page); + } else if (nbits == 32 + 8) { + concatbits(&command, 0, cmd, 0, 5, false); + exp_len += snprintf(exp + exp_len, size - exp_len, "Data"); + } else if (nbits == 5 + 32 + 8 || nbits == 5 + 32 + 1 + 8) { + concatbits(&command, 0, cmd, 0, 5, false); + + if (command == HITAGS_SELECT) { + uint8_t uid[4] = {0}; + concatbits(uid, 0, cmd, 5, 32, false); + exp_len = snprintf(exp, size, "SELECT UID: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); + } + } + } else { + exp_len = snprintf(exp, size, "Invalid command (too short)"); + } +} static const char *hts_get_type_str(uint32_t uid) { // source 1: https://www.scorpio-lk.com/downloads/Tango/HITAG_Classification.pdf diff --git a/client/src/cmdlfhitaghts.h b/client/src/cmdlfhitaghts.h index d22fa4518..522096ea4 100644 --- a/client/src/cmdlfhitaghts.h +++ b/client/src/cmdlfhitaghts.h @@ -22,7 +22,7 @@ #include "common.h" #include "hitag.h" -void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response); +void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t nbits, bool is_response); int CmdLFHitagS(const char *Cmd); diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 4ae143b1c..1d0344258 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -801,7 +801,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr annotateHitag2(explanation, sizeof(explanation), frame, data_len, parityBytes[0], hdr->isResponse, mfDicKeys, mfDicKeysCount, false); break; case PROTO_HITAGS: - annotateHitagS(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); + annotateHitagS(explanation, sizeof(explanation), frame, (data_len * 8) - ((8 - parityBytes[0]) % 8), hdr->isResponse); break; case ICLASS: annotateIclass(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); From 3c873d34bc947e6465d42834025a0a2e7d903b10 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 11:26:25 +0100 Subject: [PATCH 23/43] style --- armsrc/em4x70.c | 2 +- armsrc/hitag_common.c | 2 +- armsrc/iso14443a.c | 76 +++++++++++++++++++++---------------------- doc/commands.json | 10 +++--- 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 72eb2702c..744e5664c 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -906,7 +906,7 @@ static bool create_legacy_em4x70_bitstream_for_cmd_id(em4x70_command_bitstream_t memset(out_cmd_bitstream, 0, sizeof(em4x70_command_bitstream_t)); out_cmd_bitstream->command = EM4X70_COMMAND_ID; //uint8_t cmd = with_command_parity ? 0x3u : 0x1u; - uint8_t cmd = 0x3u; + uint8_t cmd = 0x3u; result = result && add_nibble_to_bitstream(&out_cmd_bitstream->to_send, cmd, false); out_cmd_bitstream->to_receive.bitcount = 32; if (out_cmd_bitstream->to_send.bitcount != expected_bits_to_send) { diff --git a/armsrc/hitag_common.c b/armsrc/hitag_common.c index 43837486f..d08e090a1 100644 --- a/armsrc/hitag_common.c +++ b/armsrc/hitag_common.c @@ -451,7 +451,7 @@ void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32 // Receive frame, watch for at most T0*EOF periods while (AT91C_BASE_TC1->TC_CV < T0 * HITAG_T_EOF) { - + // Check if edge in modulation is detected if (AT91C_BASE_TC1->TC_SR & next_edge_event) { next_edge_event = next_edge_event ^ (AT91C_TC_LDRAS | AT91C_TC_LDRBS); diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 3cfdc188c..bdf59f08e 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -3116,48 +3116,48 @@ int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, bool send_chaining, void *data, u if (len == 0) { BigBuf_free(); return 0; // DATA LINK ERROR - } - - - // S-Block WTX - while (len && ((data_bytes[0] & 0xF2) == 0xF2)) { - uint32_t save_iso14a_timeout = iso14a_get_timeout(); - // temporarily increase timeout - iso14a_set_timeout(MAX((data_bytes[1] & 0x3f) * save_iso14a_timeout, MAX_ISO14A_TIMEOUT)); - // Transmit WTX back - // byte1 - WTXM [1..59]. command FWT=FWT*WTXM - data_bytes[1] = data_bytes[1] & 0x3f; // 2 high bits mandatory set to 0b - // now need to fix CRC. - AddCrc14A(data_bytes, len - 2); - // transmit S-Block - ReaderTransmit(data_bytes, len, NULL); - // retrieve the result again (with increased timeout) + } + + + // S-Block WTX + while (len && ((data_bytes[0] & 0xF2) == 0xF2)) { + uint32_t save_iso14a_timeout = iso14a_get_timeout(); + // temporarily increase timeout + iso14a_set_timeout(MAX((data_bytes[1] & 0x3f) * save_iso14a_timeout, MAX_ISO14A_TIMEOUT)); + // Transmit WTX back + // byte1 - WTXM [1..59]. command FWT=FWT*WTXM + data_bytes[1] = data_bytes[1] & 0x3f; // 2 high bits mandatory set to 0b + // now need to fix CRC. + AddCrc14A(data_bytes, len - 2); + // transmit S-Block + ReaderTransmit(data_bytes, len, NULL); + // retrieve the result again (with increased timeout) data_bytes[0] = 0x00; - len = ReaderReceive(data, data_len, parity_array); - data_bytes = data; - // restore timeout - iso14a_set_timeout(save_iso14a_timeout); - } + len = ReaderReceive(data, data_len, parity_array); + data_bytes = data; + // restore timeout + iso14a_set_timeout(save_iso14a_timeout); + } - // if we received an I- or R(ACK)-Block with a block number equal to the - // current block number, toggle the current block number - if (len >= 3 // PCB+CRC = 3 bytes - && ((data_bytes[0] & 0xC0) == 0 // I-Block - || (data_bytes[0] & 0xD0) == 0x80) // R-Block with ACK bit set to 0 - && (data_bytes[0] & 0x01) == iso14_pcb_blocknum) { // equal block numbers - iso14_pcb_blocknum ^= 1; - } + // if we received an I- or R(ACK)-Block with a block number equal to the + // current block number, toggle the current block number + if (len >= 3 // PCB+CRC = 3 bytes + && ((data_bytes[0] & 0xC0) == 0 // I-Block + || (data_bytes[0] & 0xD0) == 0x80) // R-Block with ACK bit set to 0 + && (data_bytes[0] & 0x01) == iso14_pcb_blocknum) { // equal block numbers + iso14_pcb_blocknum ^= 1; + } - // if we received I-block with chaining we need to send ACK and receive another block of data - if (res) { - *res = data_bytes[0]; - } + // if we received I-block with chaining we need to send ACK and receive another block of data + if (res) { + *res = data_bytes[0]; + } - // crc check - if (len >= 3 && !CheckCrc14A(data_bytes, len)) { - BigBuf_free(); - return -1; - } + // crc check + if (len >= 3 && !CheckCrc14A(data_bytes, len)) { + BigBuf_free(); + return -1; + } if (len) { // cut frame byte diff --git a/doc/commands.json b/doc/commands.json index f3a831927..a5fe357a1 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -10102,14 +10102,16 @@ "description": "Simulate Hitag S transponder You need to `lf hitag hts eload` first", "notes": [ "lf hitag hts sim", - "lf hitag hts sim --82xx" + "lf hitag hts sim --82xx", + "lf hitag hts sim -t 30 -> set threshold to 30" ], "offline": false, "options": [ "-h, --help This help", - "-8, --82xx simulate 8268/8310" + "-8, --82xx simulate 8268/8310", + "-t, --threshold set edge detect threshold (def: 127)" ], - "usage": "lf hitag hts sim [-h8]" + "usage": "lf hitag hts sim [-h8] [-t ]" }, "lf hitag hts wrbl": { "command": "lf hitag hts wrbl", @@ -13248,6 +13250,6 @@ "metadata": { "commands_extracted": 761, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-19T08:33:58" + "extracted_on": "2025-03-19T10:24:44" } } From 53da4a25e14d718196fd5a20c9f59100fde9c862 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 11:33:06 +0100 Subject: [PATCH 24/43] added the @ry4000 special --- client/src/cmdhfmf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index c2d20cb4c..fb3d80218 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -2923,6 +2923,7 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { noValidKeyFound: PrintAndLogEx(FAILED, "No usable key was found!"); + PrintAndLogEx(HINT, "try `" _YELLOW_("hf mf autopwn -f mfc_default_keys")"` i.e. the Randy special"); free(keyBlock); free(e_sector); free(fptr); From ff1289c03d7390364b5ab841e7dae0cb40ef6efe Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:18:41 +0800 Subject: [PATCH 25/43] Update Crc16 function to use bitlength instead of length --- common/crc16.c | 13 +++++++++---- common/crc16.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/common/crc16.c b/common/crc16.c index 812cd3481..e55b20bdc 100644 --- a/common/crc16.c +++ b/common/crc16.c @@ -142,12 +142,17 @@ uint16_t update_crc16(uint16_t crc, uint8_t c) { } // two ways. msb or lsb loop. -uint16_t Crc16(uint8_t const *d, size_t length, uint16_t remainder, uint16_t polynomial, bool refin, bool refout) { - if (length == 0) +uint16_t Crc16(uint8_t const *d, size_t bitlength, uint16_t remainder, uint16_t polynomial, bool refin, bool refout) { + if (bitlength == 0) return (~remainder); - for (uint32_t i = 0; i < length; ++i) { - uint8_t c = d[i]; + uint8_t offset = 8 - (bitlength % 8); + // front padding with 0s won't change the CRC result + uint8_t prebits = 0; + for (uint32_t i = 0; i < (bitlength + 7) / 8; ++i) { + uint8_t c = prebits | d[i] >> offset; + prebits = d[i] << (8 - offset); + if (refin) c = reflect8(c); // xor in at msb diff --git a/common/crc16.h b/common/crc16.h index 80758d95b..c8aecaf21 100644 --- a/common/crc16.h +++ b/common/crc16.h @@ -47,7 +47,7 @@ typedef enum { uint16_t update_crc16_ex(uint16_t crc, uint8_t c, uint16_t polynomial); uint16_t update_crc16(uint16_t crc, uint8_t c); -uint16_t Crc16(uint8_t const *d, size_t length, uint16_t remainder, uint16_t polynomial, bool refin, bool refout); +uint16_t Crc16(uint8_t const *d, size_t bitlength, uint16_t remainder, uint16_t polynomial, bool refin, bool refout); uint16_t Crc16ex(CrcType_t ct, const uint8_t *d, size_t n); void compute_crc(CrcType_t ct, const uint8_t *d, size_t n, uint8_t *first, uint8_t *second); From 4bde83b89d6c8bb31b89f4a863fdef515e1f1e5d Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:17:47 +0800 Subject: [PATCH 26/43] =?UTF-8?q?Added=20`lf=20hitag=20htu`=20support=20fo?= =?UTF-8?q?r=20Hitag=20=C2=B5/8265?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- armsrc/Makefile | 2 +- armsrc/appmain.c | 20 + armsrc/hitagu.c | 897 +++++++++++++++++++++++++ armsrc/hitagu.h | 30 + client/CMakeLists.txt | 1 + client/Makefile | 1 + client/experimental_lib/CMakeLists.txt | 1 + client/src/cmdhf.c | 2 +- client/src/cmdlfhitag.c | 4 +- client/src/cmdlfhitagu.c | 687 +++++++++++++++++++ client/src/cmdlfhitagu.h | 32 + client/src/cmdtrace.c | 21 +- include/hitag.h | 102 ++- include/pm3_cmd.h | 8 +- include/protocols.h | 33 +- 15 files changed, 1822 insertions(+), 19 deletions(-) create mode 100644 armsrc/hitagu.c create mode 100644 armsrc/hitagu.h create mode 100644 client/src/cmdlfhitagu.c create mode 100644 client/src/cmdlfhitagu.h diff --git a/armsrc/Makefile b/armsrc/Makefile index 6ff1455de..9929b3ae3 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -72,7 +72,7 @@ else endif ifneq (,$(findstring WITH_HITAG,$(APP_CFLAGS))) - SRC_HITAG = hitag2_crypto.c hitag_common.c hitag2.c hitagS.c hitag2_crack.c + SRC_HITAG = hitag2_crypto.c hitag_common.c hitag2.c hitagS.c hitagu.c hitag2_crack.c APP_CFLAGS += -I../common/hitag2 else SRC_HITAG = diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e4e903a45..7907cc071 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -41,6 +41,7 @@ #include "hitag2.h" #include "hitag2_crack.h" #include "hitagS.h" +#include "hitagu.h" #include "em4x50.h" #include "em4x70.h" #include "iclass.h" @@ -1232,6 +1233,25 @@ static void PacketReceived(PacketCommandNG *packet) { memcpy(mem, payload->data, payload->len); break; } + + case CMD_LF_HITAGU_READ: { + lf_hitag_data_t *payload = (lf_hitag_data_t *)packet->data.asBytes; + htu_read(payload, true); + break; + } + case CMD_LF_HITAGU_WRITE: { + lf_hitag_data_t *payload = (lf_hitag_data_t *)packet->data.asBytes; + htu_write_page(payload, true); + break; + } + case CMD_LF_HITAGU_SIMULATE: { + htu_simulate((bool)packet->oldarg[0], packet->oldarg[1], packet->data.asBytes, true); + break; + } + case CMD_LF_HITAGU_UID: { + htu_read_uid(NULL, true, true); + break; + } #endif #ifdef WITH_EM4x50 diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c new file mode 100644 index 000000000..0b1216edf --- /dev/null +++ b/armsrc/hitagu.c @@ -0,0 +1,897 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency HITAG µ (micro) functions + +#include "hitagu.h" +#include "hitag_common.h" + +#include "BigBuf.h" +#include "appmain.h" // tearoff_hook() +#include "cmd.h" +#include "commonutil.h" +#include "crc16.h" +#include "dbprint.h" +#include "fpgaloader.h" +#include "hitag2/hitag2_crypto.h" +#include "lfadc.h" +#include "protocols.h" +#include "proxmark3_arm.h" +#include "string.h" +#include "ticks.h" +#include "util.h" + +// Hitag µ specific definitions +#define HTU_SOF_BITS 4 // Start of frame bits is always 3 for Hitag µ (110) plus 1 bit error flag + +MOD M = MC4K; // Modulation type + +// Structure to hold the state of the Hitag µ tag +static struct hitagU_tag tag = { + .pstate = HT_READY, // Initial state is ready + .max_page = HITAGU_MAX_PAGE_STANDARD, // Default to standard version + .icr = 0, // Default ICR value +}; + +// Macros for managing authentication state +#define IS_AUTHENTICATED() (tag.pstate == HT_AUTHENTICATE) +#define SET_AUTHENTICATED() (tag.pstate = HT_AUTHENTICATE) +#define RESET_AUTHENTICATION() (tag.pstate = HT_READY) + +/* + * Update the maximum page number based on the tag's ICR (IC Revision) + */ +static void update_tag_max_page_by_icr(void) { + // Set max_page based on ICR value + switch (tag.icr) { + case HITAGU_ICR_STANDARD: + tag.max_page = HITAGU_MAX_PAGE_STANDARD; + DBG Dbprintf("Detected standard Hitag μ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_ADVANCED: + tag.max_page = HITAGU_MAX_PAGE_ADVANCED; + DBG Dbprintf("Detected Hitag μ advanced (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_ADVANCED_PLUS: + tag.max_page = HITAGU_MAX_PAGE_ADVANCED_PLUS; + DBG Dbprintf("Detected Hitag μ advanced+ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_8265: + tag.max_page = HITAGU_MAX_PAGE_8265; + DBG Dbprintf("Detected Hitag μ 8265 (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + default: + // Unknown ICR, use standard size as fallback + tag.max_page = HITAGU_MAX_PAGE_STANDARD; + DBG Dbprintf("Unknown Hitag μ ICR: 0x%02X, defaulting to max page: 0x%02X", tag.icr, tag.max_page); + break; + } +} + +/* + * Update the maximum page number based on the tag's memory configuration + * This function checks both ICR and additional pattern-based detection + */ +static void update_tag_max_page(void) { + // First try to determine max_page from ICR + update_tag_max_page_by_icr(); + + // Additional tag type detection can be added here +} + +/* + * Handles all commands from a reader for Hitag µ + * Processes flags and commands, generates appropriate responses + */ +static void htu_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_t *tx, size_t *txlen) { + // Initialize response + *txlen = 0; + + if (rxlen < 5) { + return; // Command too short + } + + // Extract flags (5 bits) and command (6 bits if present) + uint8_t flags = (rx[0] >> 3) & 0x1F; + uint8_t command = 0; + + if (rxlen >= 11) { + // Extract 6-bit command if present + command = ((rx[0] & 0x07) << 3) | ((rx[1] >> 5) & 0x07); + } + + // Check flags + bool inv_flag = (flags & HITAGU_FLAG_INV); + bool crct_flag = (flags & HITAGU_FLAG_CRCT); + + // Handle based on flags and command + if (inv_flag) { + // Inventory mode - respond with UID (48 bits) + *txlen = concatbits(tx, *txlen, tag.uid, 0, HITAGU_UID_SIZE * 8, true); + } else if (command == HITAGU_CMD_LOGIN) { + // Login command + if (rxlen >= 43) { // 5+6+32 bits = 43 bits minimum + // Extract password - 32 bits after command + uint32_t password = 0; + for (int i = 0; i < 4; i++) { + int startBit = 11 + i * 8; // 5+6 bits of command + i*8 + uint8_t b = 0; + + for (int j = 0; j < 8; j++) { + int bitPos = startBit + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + b |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + password |= (b << (24 - i * 8)); + } + + // Check password + if (password == ((tag.password[0] << 24) | (tag.password[1] << 16) | (tag.password[2] << 8) | tag.password[3])) { + // Set authentication state + SET_AUTHENTICATED(); + + // Send success response + uint8_t resp_byte = 0x01; // Success code + *txlen = concatbits(tx, *txlen, &resp_byte, 0, 8, true); + } else { + // Authentication failed + RESET_AUTHENTICATION(); + + // Send failure response + uint8_t resp_byte = 0x00; // Failure code + *txlen = concatbits(tx, *txlen, &resp_byte, 0, 8, true); + } + } + } else if (command == HITAGU_CMD_SELECT) { + // Select command + if (rxlen >= 59) { // 5+6+48 bits = 59 bits minimum (48-bit UID) + // Extract UID to select - next 48 bits + uint8_t sel_uid[6] = {0}; + for (int i = 0; i < 6; i++) { + int startBit = 11 + i * 8; // 5+6 bits of command + i*8 + uint8_t b = 0; + + for (int j = 0; j < 8; j++) { + int bitPos = startBit + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + b |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + sel_uid[i] = b; + } + + // Check if UID matches + if (memcmp(sel_uid, tag.uid, 6) == 0) { + // Selected - send response with select data + uint8_t resp_data[4] = {0xCA, 0x24, 0x00, 0x00}; // Standard select response + *txlen = concatbits(tx, *txlen, resp_data, 0, 32, true); + } else { + // UID mismatch - no response + *txlen = 0; + } + } + } else if (command == HITAGU_CMD_READ_MULTIPLE_BLOCK) { + // Read command + if (rxlen >= 19) { // 5+6+8 bits = 19 bits minimum + // Extract page address - 8 bits after command + uint8_t page = 0; + for (int i = 0; i < 8; i++) { + int bitPos = 11 + i; // 5+6 bits of command + i + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + page |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + + // Extract number of blocks to read if ADR flag is set + uint8_t read_len = 1; // Default to 1 page + if ((flags & HITAGU_FLAG_ADR) && rxlen >= 27) { + for (int i = 0; i < 8; i++) { + int bitPos = 19 + i; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + if (pos < (rxlen + 7) / 8) { + read_len |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + } + } + + // Security check: does this page require authentication? + bool needs_auth = false; + // Check if page is password-protected (e.g., config or password page) + if (page == HITAGU_PASSWORD_PADR) { + needs_auth = true; + } + + // Check authentication for protected pages + if (needs_auth && !IS_AUTHENTICATED()) { + // Not authenticated, cannot read protected pages + DBG Dbprintf("Page %d requires authentication", page); + + // Mark as unauthorized access + *txlen = 0; // No response + } else { + // Map page address (some pages may be aliased) + uint8_t real_page = page; + if (page >= 64 && tag.max_page <= 64) { + real_page = page & 0x3F; // Pages above 64 map to 0-63 + } + + // Read requested number of pages + for (int i = 0; i < read_len && i < 16; i++) { // Limit to 16 pages max + uint8_t curr_page = (real_page + i) % tag.max_page; + + // Special pages + if (curr_page == HITAGU_CONFIG_PADR) { + // Config page + *txlen = concatbits(tx, *txlen, (uint8_t *)&tag.config, 0, 32, true); + } else if (curr_page == HITAGU_PASSWORD_PADR) { + // Password page - only return if authenticated + if (IS_AUTHENTICATED()) { + *txlen = concatbits(tx, *txlen, tag.password, 0, 32, true); + } else { + // Return zeros if not authenticated + uint8_t zeros[4] = {0}; + *txlen = concatbits(tx, *txlen, zeros, 0, 32, true); + } + } else { + // Regular page + *txlen = concatbits(tx, *txlen, tag.data.pages[curr_page], 0, 32, true); + } + } + } + } + } else if (command == HITAGU_CMD_WRITE_SINGLE_BLOCK) { + // Write command + if (rxlen >= 51) { // 5+6+8+32 bits = 51 bits minimum + // Check if authenticated + if (!IS_AUTHENTICATED()) { + DBG Dbprintf("WRITE failed: not authenticated"); + *txlen = 0; // No response + } else { + // Extract page address - 8 bits after command + uint8_t page = 0; + for (int i = 0; i < 8; i++) { + int bitPos = 11 + i; // 5+6 bits of command + i + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + page |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + + // Extract data - 32 bits after page address + uint8_t data[4] = {0}; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + int bitPos = 19 + i * 8 + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + if (pos < (rxlen + 7) / 8) { + data[i] |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + } + } + + // Map page address + uint8_t real_page = page; + if (page >= 64 && tag.max_page <= 64) { + real_page = page & 0x3F; // Pages above 64 map to 0-63 + } + + // Special pages + if (real_page == HITAGU_CONFIG_PADR) { + // Write config + memcpy(&tag.config, data, 4); + DBG Dbprintf("WRITE CONFIG: %02X %02X %02X %02X", data[0], data[1], data[2], data[3]); + } else if (real_page == HITAGU_PASSWORD_PADR) { + // Write password + memcpy(tag.password, data, 4); + DBG Dbprintf("WRITE PASSWORD: %02X %02X %02X %02X", data[0], data[1], data[2], data[3]); + } else if (real_page < tag.max_page) { + // Regular page + memcpy(tag.data.pages[real_page], data, 4); + DBG Dbprintf("WRITE PAGE %02X: %02X %02X %02X %02X", real_page, data[0], data[1], data[2], data[3]); + } + + // Send success acknowledgment + uint8_t ack = 0x01; // Success acknowledgment + *txlen = concatbits(tx, *txlen, &ack, 0, 8, true); + } + } + } else if (command == HITAGU_CMD_SYSINFO) { + // System info command + // Prepare system info response with ICR field + uint8_t info[8] = {0}; + + // First byte: Error flag (0) + 7 reserved bits + info[0] = 0x00; + + // Additional bytes: System Memory Block Data + // MSN (Manufacturer Serial Number) - example values + info[1] = 0x12; + info[2] = 0x34; + + // MFC (Manufacturer Code) - example value + info[3] = 0x04; // NXP + + // ICR (IC Revision) + info[4] = tag.icr; + + // Reserved bytes + info[5] = 0x00; + info[6] = 0x00; + info[7] = 0x00; + + // Add the system info data to the response + *txlen = concatbits(tx, *txlen, info, 0, 64, true); + } else if (flags == HITAGU_CMD_STAY_QUIET) { + // Quiet command - no response needed + RESET_AUTHENTICATION(); + *txlen = 0; + } else { + // Unknown command + DBG Dbprintf("Unknown command or flags: flags=%02X, cmd=%02X", flags, command); + *txlen = 0; // No response + } + + // Add CRC if requested and there is response data + if (crct_flag && *txlen > 0) { + // Calculate CRC-16/XMODEM directly from tx + uint16_t crc = Crc16(tx, *txlen, 0, CRC16_POLY_CCITT, false, true); + + // Append CRC-16 (16 bits) + *txlen = concatbits(tx, *txlen, (uint8_t *)&crc, 0, 16, true); + } +} + +/* + * Simulates a Hitag µ Tag with the given data + */ +void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol) { + + uint8_t rx[HITAG_FRAME_LEN] = {0}; + size_t rxlen = 0; + uint8_t tx[HITAG_FRAME_LEN]; + size_t txlen = 0; + + // Free any allocated BigBuf memory + BigBuf_free(); + BigBuf_Clear_ext(false); + + DbpString("Starting Hitag µ simulation"); + + // Reset tag state + memset(&tag, 0, sizeof(tag)); + tag.max_page = 64; // Default maximum page + RESET_AUTHENTICATION(); + + // Read tag data into memory if supplied + if (tag_mem_supplied) { + DbpString("Loading Hitag µ memory..."); + // First 6 bytes are the UID (48 bits) + memcpy(tag.uid, data, 6); + // Rest is page data + memcpy(tag.data.pages, data + 6, sizeof(tag.data.pages)); + } + + // Update max_page based on configuration + update_tag_max_page(); + + // Debug output of tag data + DBG Dbprintf("UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); + + for (int i = 0; i <= tag.max_page; i++) { + DBG Dbprintf("Page[%2d]: %02X %02X %02X %02X", i, tag.data.pages[i][0], tag.data.pages[i][1], + tag.data.pages[i][2], tag.data.pages[i][3]); + } + + hitag_setup_fpga(0, threshold, ledcontrol); + + int overflow = 0; + + // Simulation main loop + while ((BUTTON_PRESS() == false) && (data_available() == false)) { + uint32_t start_time = 0; + + WDT_HIT(); + + // Receive commands from the reader + hitag_tag_receive_frame(rx, sizeof(rx), &rxlen, &start_time, ledcontrol, &overflow); + + // Check if frame was captured and store it + if (rxlen > 0) { + LogTraceBits(rx, rxlen, start_time, TIMESTAMP, true); + + // Disable timer 1 with external trigger to avoid triggers during our own modulation + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + + // Prepare tag response (tx) + memset(tx, 0x00, sizeof(tx)); + txlen = 0; + + // Process received reader command + htu_handle_reader_command(rx, rxlen, tx, &txlen); + + // Wait for HITAG_T_WAIT_RESP carrier periods after the last reader bit, + // not that since the clock counts since the rising edge, but T_Wait1 is + // with respect to the falling edge, we need to wait actually (T_Wait1 - T_Low) + // periods. The gap time T_Low varies (4..10). All timer values are in + // terms of T0 units + while (AT91C_BASE_TC0->TC_CV < T0 * (HITAG_T_WAIT_RESP - HITAG_T_LOW)) { + }; + + // Send and store the tag answer (if there is any) + if (txlen > 0) { + // Transmit the tag frame + start_time = TIMESTAMP; + hitag_tag_send_frame(tx, txlen, HTU_SOF_BITS, MC4K, ledcontrol); + LogTraceBits(tx, txlen, start_time, TIMESTAMP, false); + } + + // Enable and reset external trigger in timer for capturing future frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + + // Reset the received frame and response timing info + memset(rx, 0x00, sizeof(rx)); + } + + // Reset the frame length + rxlen = 0; + // Save the timer overflow, will be 0 when frame was received + overflow += (AT91C_BASE_TC1->TC_CV / T0); + // Reset the timer to restart while-loop that receives frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG; + } + + hitag_cleanup(ledcontrol); + // Release allocated memory from BigBuf + BigBuf_free(); + + DbpString("Simulation stopped"); +} + +/* + * Send command to reader and receive answer from tag + */ +static int htu_reader_send_receive(uint8_t *tx, size_t txlen, uint8_t *rx, size_t sizeofrx, size_t *rxlen, + uint32_t t_wait, bool ledcontrol, uint8_t modulation, uint8_t sof_bits) { + // Reset the received frame + memset(rx, 0x00, sizeofrx); + + // Disable timer 1 with external trigger to avoid triggers during our own modulation + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + + DBG Dbprintf("tx %d bits:", txlen); + DBG Dbhexdump((txlen + 7) / 8, tx, false); + + // Wait until we can send the command + while (AT91C_BASE_TC0->TC_CV < T0 * t_wait) { + }; + + // Set up tracing + uint32_t start_time = TIMESTAMP; + + // Send the command - Hitag µ always requires SOF + hitag_reader_send_frame(tx, txlen, ledcontrol, true); + + // if (enable_page_tearoff && tearoff_hook() == PM3_ETEAROFF) { + // return PM3_ETEAROFF; + // } + + LogTraceBits(tx, txlen, start_time, TIMESTAMP, true); + + // Enable and reset external trigger in timer for capturing future frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + + // Capture response - SOF is automatically stripped by hitag_reader_receive_frame + hitag_reader_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol, modulation, sof_bits); + + LogTraceBits(rx, *rxlen, start_time, TIMESTAMP, false); + DBG Dbprintf("rx %d bits:", *rxlen); + DBG Dbhexdump((*rxlen + 7) / 8, rx, false); + + // TODO: check Error flag + + return PM3_SUCCESS; +} + +/* + * Selects a tag using the READ UID, GET SYSTEM INFORMATION, and LOGIN commands + */ +static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t sizeoftx, uint8_t *rx, size_t sizeofrx, + int t_wait, bool ledcontrol) { + // Initialize response + size_t txlen = 0; + size_t rxlen = 0; + + // Setup FPGA and initialize + hitag_setup_fpga(FPGA_LF_EDGE_DETECT_READER_FIELD, 127, ledcontrol); + + // Prepare common flags and command variables + uint8_t flags = HITAGU_FLAG_CRCT; // Set appropriate flags for all commands + uint8_t command; + // uint8_t parameter; + + // 1. Send READ UID command + command = HITAGU_CMD_READ_UID; + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // lf cmdread -d 64 -z 96 -o 160 -e W2400 -e S224 -e E336 -s 4096 -c W0S00100010000 + + // Send the READ UID command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, t_wait, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if the response is valid + if (rxlen < 1 + 48 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Read UID command failed! %i", rxlen); + return -1; // Read UID failed + } + + // Process the UID from the response + concatbits(tag.uid, 0, rx, 1, 48, false); + + // 2. Send GET SYSTEM INFORMATION command + command = HITAGU_CMD_SYSINFO; + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send the GET SYSTEM INFORMATION command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + concatbits(&tag.icr, 0, rx, 9, 8, false); + + // Check if the response is valid + if (rxlen < 1 + 16 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + // 8265 bug? sometile lost Data field first bit + DBG Dbprintf("Get System Information command failed! %i", rxlen); + return -2; // Get System Information failed + } + + // 3. Read config block + command = HITAGU_CMD_READ_MULTIPLE_BLOCK; + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Add block address + txlen = concatbits(tx, txlen, (uint8_t *)&"\xFF", 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, (uint8_t *)&"\x00", 0, 8, true); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if the response is valid + if (rxlen < 1 + 32 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Read config block command failed! %i", rxlen); + return -2; // Read config block failed + } + + // Process the config block from the response + concatbits(tag.config.asBytes, 0, rx, 1, 32, false); + reverse_arraybytes(tag.config.asBytes, HITAGU_BLOCK_SIZE); + + // 4. Send LOGIN command if necessary + if (payload && (payload->cmd == HTUF_82xx || payload->cmd == HTUF_PASSWORD)) { + command = HITAGU_CMD_LOGIN; // Set command for login + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + txlen = concatbits(tx, txlen, payload->pwd, 0, HITAG_PASSWORD_SIZE * 8, false); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send the LOGIN command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if login succeeded + if (rxlen < 1 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Login command failed! %i", rxlen); + return -3; // Login failed + } else { + DBG DbpString("Login successful"); + } + + // flags |= HITAGU_FLAG_ADR; + // command = HITAGU_CMD_LOGIN; // Set command for login + // txlen = 0; // Reset txlen for the new command + // txlen = concatbits(tx, txlen, &flags, 0, 5, true); + // txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // txlen = concatbits(tx, txlen, payload->uid, 0, HITAGU_UID_SIZE * 8, false); + // txlen = concatbits(tx, txlen, payload->pwd, 0, HITAG_PASSWORD_SIZE * 8, false); + + // // Append CRC-16 (16 bits) + // crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + // txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // // Send the LOGIN command and receive the response + // htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // // Check if login succeeded + // if (rxlen < 1 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + // DBG Dbprintf("Login command failed! %i", rxlen); + // return -3; // Login failed + // } else { + // DbpString("Login successful"); + // } + } + + // If all commands are successful, update the tag's state + update_tag_max_page(); // Update max_page based on the new configuration + + return 0; // Selection successful +} + +/* + * Reads the UID of a Hitag µ tag using the INVENTORY command + */ +int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer) { + // Initialize response + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // Use htu_select_tag to select the card and retrieve UID + int reason = htu_select_tag(NULL, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + DBG DbpString("Error: htu_read_uid Failed to select tag"); + status = PM3_ERFTRANS; + goto exit; + } + + DBG Dbprintf("HitagU UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); + + if (uid) { + *uid = BSWAP_48(*(uint64_t*)&tag.uid); + } + +exit: + hitag_cleanup(ledcontrol); + + if (send_answer) { + // Send UID + reply_reason(CMD_LF_HITAGU_UID, status, reason, tag.uid, sizeof(tag.uid)); + } + + // Reset authentication state + RESET_AUTHENTICATION(); + + return status; +} + +/* + * Reads a Hitag µ tag + */ +void htu_read(const lf_hitag_data_t *payload, bool ledcontrol) { + size_t rxlen = 0; + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + size_t txlen = 0; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // DBG { + // DbpString("htu_read"); + // Dbprintf("payload->page: %d", payload->page); + // Dbprintf("payload->page_count: %d", payload->page_count); + // Dbprintf("payload->cmd: %d", payload->cmd); + // Dbprintf("payload->uid: %02X%02X%02X%02X%02X%02X", payload->uid[0], payload->uid[1], payload->uid[2], + // payload->uid[3], payload->uid[4], payload->uid[5]); + // Dbprintf("payload->key: %02X%02X%02X%02X%02X%02X", payload->key[0], payload->key[1], payload->key[2], + // payload->key[3], payload->key[4], payload->key[5]); + // Dbprintf("payload->pwd: %02X%02X%02X%02X", payload->pwd[0], payload->pwd[1], payload->pwd[2], payload->pwd[3]); + // } + + // Use htu_select_tag to select the card and retrieve UID + int reason = htu_select_tag(payload, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + DbpString("Error: htu_read Failed to select tag"); + status = PM3_ERFTRANS; + goto exit; + } + + lf_htu_read_response_t card = { + .icr = tag.icr, + }; + memcpy(card.uid, tag.uid, HITAGU_UID_SIZE); + memcpy(card.config_page.asBytes, tag.config.asBytes, HITAGU_BLOCK_SIZE); + + uint8_t page_count_index = payload->page_count - 1; + + if (payload->page_count == 0) { + page_count_index = tag.max_page - payload->page - 1; + } + + // Step 2: Process the actual command + // Add flags (5 bits) + uint8_t flags = HITAGU_FLAG_CRCT; + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + + // Read command format: [UID] [CRC-16] + + // Add command (6 bits) + uint8_t command = HITAGU_CMD_READ_MULTIPLE_BLOCK; + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // The 8265 chip has known issues when reading multiple blocks: + // - page_count = 1: Works correctly + // - page_count >= 2: Data field is left shifted by 1 bit + // - page_count = 2 or page+page_count exceeds valid page: Data field has an extra '1' bit + // - page_count = 3,4: Data field last bit is always '1' + // - page_count = 5: CRC is not appended + // - page_count >= 6: May cause next command to have no response + // Workaround: Read one block at a time + if (payload->mode == 0 /**for debug */ + && (payload->cmd == HTUF_82xx || tag.icr == HITAGU_ICR_8265 || !memcmp(tag.uid, "\x00\x00\x00\x00\x00\x00", 6))) { + + uint8_t page_addr; + for (int i = 0; i <= page_count_index; i++) { + page_addr = payload->page + i; + + txlen = 5 + 6; // restore txlen for the new command + txlen = concatbits(tx, txlen, &page_addr, 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, (uint8_t *)&"\x00", 0, 8, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + if (flags & HITAGU_FLAG_CRCT && Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Error: response CRC invalid"); + card.pages_reason[i] = -1; + continue; + } + + // Check response + if (rxlen < 1 + HITAGU_BLOCK_SIZE * 8 + (flags & HITAGU_FLAG_CRCT ? 16 : 0)) { + DbpString("Error: invalid response received after read command"); + card.pages_reason[i] = -11; + } else { + DBG Dbprintf("Read successful, response: %d bits", rxlen); + // todo: For certain pages, update our cached data + card.pages_reason[i] = 1; + concatbits(card.pages[i], 0, rx, 1, HITAGU_BLOCK_SIZE * 8, false); + } + } + } else { + txlen = concatbits(tx, txlen, &payload->page, 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, &page_count_index, 0, 8, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + if (flags & HITAGU_FLAG_CRCT && Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Error: response CRC invalid"); + status = PM3_ERFTRANS; + goto exit; + } + + // Check response + if (rxlen < 1 + HITAGU_BLOCK_SIZE * 8 + (flags & HITAGU_FLAG_CRCT ? 16 : 0)) { + DbpString("Error: invalid response received after read command"); + status = PM3_ERFTRANS; + } else { + DBG Dbprintf("Read successful, response: %d bits", rxlen); + // todo: For certain pages, update our cached data + concatbits((uint8_t *)card.pages, 0, rx, 1, rxlen - 1 - 16, false); + for (int i = 0; i < (rxlen - 1 - 16) / (HITAGU_BLOCK_SIZE * 8); i++) { + card.pages_reason[i] = 1; + } + } + } + +exit: + hitag_cleanup(ledcontrol); + // Send status to client + reply_reason(CMD_LF_HITAGU_READ, status, reason, (uint8_t *)&card, sizeof(card)); +} + +/* + * Writes a page to a Hitag µ tag + */ +void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { + size_t rxlen = 0; + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + size_t txlen = 0; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // DBG { + // DbpString("htu_write_page"); + // Dbprintf("payload->page: %d", payload->page); + // Dbprintf("payload->data: %02X%02X%02X%02X", payload->data[0], payload->data[1], payload->data[2], payload->data[3]); + // Dbprintf("payload->cmd: %d", payload->cmd); + // Dbprintf("payload->uid: %02X%02X%02X%02X%02X%02X", payload->uid[0], payload->uid[1], payload->uid[2], payload->uid[3], payload->uid[4], payload->uid[5]); + // Dbprintf("payload->key: %02X%02X%02X%02X%02X%02X", payload->key[0], payload->key[1], payload->key[2], payload->key[3], payload->key[4], payload->key[5]); + // Dbprintf("payload->pwd: %02X%02X%02X%02X", payload->pwd[0], payload->pwd[1], payload->pwd[2], payload->pwd[3]); + // Dbprintf("payload->mode: %d", payload->mode); + // } + + int reason = htu_select_tag(payload, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + status = PM3_ERFTRANS; + goto exit; + } + + // Step 2: Send write command + uint8_t flags = HITAGU_FLAG_CRCT; + + // Add flags (5 bits) for write operation + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + + // Add write command (6 bits) + uint8_t command = HITAGU_CMD_WRITE_SINGLE_BLOCK; + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Add page address (8 bits) + txlen = concatbits(tx, txlen, &payload->page, 0, 8, true); + + // Add data to write (32 bits) + txlen = concatbits(tx, txlen, payload->data, 0, 32, false); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + DBG Dbprintf("Writing to page 0x%02X", payload->page); + + // Send write command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check response + if (payload->cmd == HTUF_82xx && rxlen == 0) { + // 8265 bug? no response on successful write command + reason = 0; + status = PM3_ENODATA; + } else if (rxlen != 1 + 16) { + DbpString("Error: htu_write_page No valid response received after write command"); + reason = -1; + status = PM3_ERFTRANS; + } else { + DBG Dbprintf("Write successful, response: %d bits", rxlen); + } + +exit: + hitag_cleanup(ledcontrol); + reply_reason(CMD_LF_HITAGU_WRITE, status, reason, NULL, 0); +} diff --git a/armsrc/hitagu.h b/armsrc/hitagu.h new file mode 100644 index 000000000..a1144e1e7 --- /dev/null +++ b/armsrc/hitagu.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Hitag µ functions +//----------------------------------------------------------------------------- + +#ifndef _HITAGU_H_ +#define _HITAGU_H_ + +#include "common.h" +#include "hitag.h" + +void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol); +void htu_read(const lf_hitag_data_t *payload, bool ledcontrol); +void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol); +int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer); + +#endif diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 274024fb7..854828df9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -380,6 +380,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfhid.c ${PM3_ROOT}/client/src/cmdlfhitag.c ${PM3_ROOT}/client/src/cmdlfhitaghts.c + ${PM3_ROOT}/client/src/cmdlfhitagu.c ${PM3_ROOT}/client/src/cmdlfidteck.c ${PM3_ROOT}/client/src/cmdlfindala.c ${PM3_ROOT}/client/src/cmdlfio.c diff --git a/client/Makefile b/client/Makefile index cf4b65b5f..f743d6968 100644 --- a/client/Makefile +++ b/client/Makefile @@ -660,6 +660,7 @@ SRCS = mifare/aiddesfire.c \ cmdlfhid.c \ cmdlfhitag.c \ cmdlfhitaghts.c \ + cmdlfhitagu.c \ cmdlfidteck.c \ cmdlfindala.c \ cmdlfio.c \ diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 1e17d3361..7d0c952b1 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -380,6 +380,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfhid.c ${PM3_ROOT}/client/src/cmdlfhitag.c ${PM3_ROOT}/client/src/cmdlfhitaghts.c + ${PM3_ROOT}/client/src/cmdlfhitagu.c ${PM3_ROOT}/client/src/cmdlfidteck.c ${PM3_ROOT}/client/src/cmdlfindala.c ${PM3_ROOT}/client/src/cmdlfio.c diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index c38e6da03..ae5f8b845 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -83,7 +83,7 @@ int CmdHFSearch(const char *Cmd) { int res = PM3_ESOFT; - uint8_t success[20] = {0}; + uint8_t success[COUNT_OF_PROTOCOLS] = {0}; PROMPT_CLEARLINE; PrintAndLogEx(INPLACE, " Searching for ThinFilm tag..."); diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 2cafdb161..81e38edc4 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -16,6 +16,8 @@ // Low frequency Hitag support //----------------------------------------------------------------------------- #include "cmdlfhitag.h" +#include "cmdlfhitaghts.h" +#include "cmdlfhitagu.h" #include #include "cmdparser.h" // command_t #include "comms.h" @@ -31,7 +33,6 @@ #include "pm3_cmd.h" // return codes #include "hitag2/hitag2_crypto.h" #include "util_posix.h" // msclock -#include "cmdlfhitaghts.h" static int CmdHelp(const char *Cmd); @@ -2458,6 +2459,7 @@ static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdLFHitagList, AlwaysAvailable, "List Hitag trace history"}, {"hts", CmdLFHitagS, AlwaysAvailable, "{ Hitag S/8211 operations }"}, + {"htu", CmdLFHitagU, AlwaysAvailable, "{ Hitag µ/8265 operations }"}, {"-----------", CmdHelp, IfPm3Hitag, "------------------------ " _CYAN_("General") " ------------------------"}, {"info", CmdLFHitagInfo, IfPm3Hitag, "Hitag 2 tag information"}, {"reader", CmdLFHitagReader, IfPm3Hitag, "Act like a Hitag 2 reader"}, diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c new file mode 100644 index 000000000..0915037fa --- /dev/null +++ b/client/src/cmdlfhitagu.c @@ -0,0 +1,687 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency Hitag µ support +//----------------------------------------------------------------------------- +#include "cmdlfhitagu.h" + +#include "cliparser.h" +#include "cmddata.h" // setDemodBuff +#include "cmdparser.h" // command_t +#include "cmdtrace.h" +#include "commonutil.h" +#include "comms.h" +#include "crc16.h" +#include "fileutils.h" // savefile +#include "graph.h" // MAX_GRAPH_TRACE_LEN +#include "hitag.h" +#include "hitag2/hitag2_crypto.h" +#include "lfdemod.h" +#include "pm3_cmd.h" // return codes +#include "protocols.h" // defines +#include "util_posix.h" // msclock +#include + +static int CmdHelp(const char *Cmd); + +uint8_t hitagu_CRC_check(uint8_t *d, uint32_t nbit) { + if (nbit < 9) { + return 2; + } + + return (Crc16(d, nbit, 0, CRC16_POLY_CCITT, false, false) == 0); +} + +void annotateHitagU(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { + + if (is_response) { + + } else { + uint8_t flag = reflect8(cmd[0]) & 0x1F; + uint8_t command = ((reflect8(cmd[0]) >> 5) & 0x07) | ((reflect8(cmd[1]) & 0x07) << 3); + bool has_uid = false; + + size_t exp_len = snprintf(exp, size, "Flg:"); + + if (flag & HITAGU_FLAG_PEXT) { + exp_len += snprintf(exp + exp_len, size - exp_len, " PEXT"); + } + if (flag & HITAGU_FLAG_INV) { + exp_len += snprintf(exp + exp_len, size - exp_len, " INV"); + if (flag & HITAGU_FLAG_RFU) { + exp_len += snprintf(exp + exp_len, size - exp_len, " RFU"); + } + if (flag & HITAGU_FLAG_NOS) { + exp_len += snprintf(exp + exp_len, size - exp_len, " NOS"); + } + } else { + if (flag & HITAGU_FLAG_SEL) { + exp_len += snprintf(exp + exp_len, size - exp_len, " SEL"); + } + if (flag & HITAGU_FLAG_ADR) { + exp_len += snprintf(exp + exp_len, size - exp_len, " ADR"); + has_uid = true; + } + } + if (flag & HITAGU_FLAG_CRCT) { + exp_len += snprintf(exp + exp_len, size - exp_len, " CRCT"); + } + + exp_len += snprintf(exp + exp_len, size - exp_len, "|Cmd: "); + switch (command) { + case HITAGU_CMD_LOGIN: { + bool has_mfc = false; + if (cmdsize == 6 + has_uid * HITAGU_UID_SIZE || cmdsize == 8 + has_uid * HITAGU_UID_SIZE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "8265 LOGIN"); + } else if (cmdsize == 7 + has_uid * HITAGU_UID_SIZE || cmdsize == 9 + has_uid * HITAGU_UID_SIZE) { + uint8_t mfc; + concatbits(&mfc, 0, cmd, 5 + 6 + 8 + 32, 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, "LOGIN mfc:%02x ", mfc); + has_mfc = true; + } + if (has_uid) { + uint8_t uid[HITAGU_UID_SIZE]; + concatbits(uid, 0, cmd, 5 + 6 + has_mfc * 8 + 32, HITAGU_UID_SIZE * 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, " uid:%s", sprint_hex_inrow(uid, HITAGU_UID_SIZE)); + } + uint8_t password[HITAG_PASSWORD_SIZE]; + concatbits(password, 0, cmd, 5 + 6 + has_mfc * 8 + has_uid * HITAGU_UID_SIZE * 8, HITAG_PASSWORD_SIZE * 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, " pwd:%s", sprint_hex_inrow(password, HITAG_PASSWORD_SIZE)); + break; + } + case HITAGU_CMD_INVENTORY: + exp_len += snprintf(exp + exp_len, size - exp_len, "INVENTORY"); + break; + case HITAGU_CMD_READ_MULTIPLE_BLOCK: { + uint8_t block_addr; + concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); + + uint8_t block_count; + concatbits(&block_count, 0, cmd, 5 + 6 + 8, 8, false); + + exp_len += snprintf(exp + exp_len, size - exp_len, "READ MULTIPLE BLOCK start:%d num:%d", reflect8(block_addr), reflect8(block_count)); + break; + } + case HITAGU_CMD_WRITE_SINGLE_BLOCK: { + uint8_t block_addr; + concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); + + uint8_t block_data[4]; + concatbits(block_data, 0, cmd, 5 + 6 + 8, 32, false); + + exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE SINGLE BLOCK start:%d data:[%s]", + reflect8(block_addr), sprint_hex_inrow(block_data, 4)); + break; + } + case HITAGU_CMD_SELECT: + exp_len += snprintf(exp + exp_len, size - exp_len, "SELECT"); + break; + case HITAGU_CMD_SYSINFO: + exp_len += snprintf(exp + exp_len, size - exp_len, "GET SYSTEM INFORMATION"); + break; + case HITAGU_CMD_READ_UID: + exp_len += snprintf(exp + exp_len, size - exp_len, "READ UID"); + break; + case HITAGU_CMD_STAY_QUIET: + exp_len += snprintf(exp + exp_len, size - exp_len, "STAY QUIET"); + break; + default: + exp_len += snprintf(exp + exp_len, size - exp_len, "Unknown 0x%02X", command); + break; + } + } +} + +static bool htu_get_uid(uint64_t *uid) { + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_UID, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_UID, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return false; + } + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag µ UID"); + return false; + } + + if (uid) { + *uid = bytes_to_num(resp.data.asBytes, HITAGU_UID_SIZE); + } + return true; +} + +int read_htu_uid(void) { + uint64_t uid = 0; + if (htu_get_uid(&uid) == false) { + return PM3_ESOFT; + } + + PrintAndLogEx(SUCCESS, "UID.... " _GREEN_("%012llX"), uid); + // PrintAndLogEx(SUCCESS, "TYPE... " _GREEN_("%s"), htu_get_type_str(uid)); + return PM3_SUCCESS; +} + +static int process_hitagu_common_args(CLIParserContext *ctx, lf_hitag_data_t *const packet) { + bool use_82xx = arg_get_lit(ctx, 1); + bool use_password = false; + uint8_t key[HITAG_PASSWORD_SIZE]; + int key_len = 0; + + int res = CLIParamHexToBuf(arg_get_str(ctx, 2), key, HITAG_PASSWORD_SIZE, &key_len); + if (res != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + if (key_len != 0 && key_len != HITAG_PASSWORD_SIZE) { + PrintAndLogEx(WARNING, "Wrong KEY len expected 0 or 4, got %d", key_len); + return PM3_EINVARG; + } + + // complete options + if (key_len == 0 && use_82xx) { + memcpy(key, "\x00\x00\x00\x00", 4); + key_len = 4; + } else if (key_len != 0) { + use_password = true; + } + + memset(packet, 0, sizeof(*packet)); + + if (use_82xx) { + packet->cmd = HTUF_82xx; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in 82xx mode"); + } else if (use_password) { + packet->cmd = HTUF_PASSWORD; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in password mode"); + } else { + packet->cmd = HTUF_PLAIN; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Access " _YELLOW_("Hitag µ") " in Plain mode"); + } + + return PM3_SUCCESS; +} + +static void print_error(int8_t reason) { + switch (reason) { + case 0: + PrintAndLogEx(FAILED, "No data"); + break; + case -2: + PrintAndLogEx(FAILED, "UID Request failed!"); + break; + case -3: + PrintAndLogEx(FAILED, "Select UID failed!"); + break; + case -4: + PrintAndLogEx(FAILED, "No write access on block. Not authorized?"); + break; + case -5: + PrintAndLogEx(FAILED, "Write failed! Wrong password?"); + break; + case -6: + PrintAndLogEx(FAILED, "Error, " _YELLOW_("AUT=1") " This tag is configured in Authentication Mode"); + break; + case -7: + PrintAndLogEx(FAILED, "Error, unknown function"); + break; + case -8: + PrintAndLogEx(FAILED, "Authenticate failed!"); + break; + case -9: + PrintAndLogEx(FAILED, "No write access on block"); + break; + case -10: + PrintAndLogEx(FAILED, "Write to block failed!"); + break; + case -11: + PrintAndLogEx(FAILED, "Read block failed!"); + break; + default: + // PM3_REASON_UNKNOWN + PrintAndLogEx(FAILED, "Error - Hitag µ failed"); + } +} + +static void hitagu_config_print(hitagu_config_t config) { + PrintAndLogEx(INFO, " Data Rate......... %s", (const char *[]) {"2 kbit/s", "4 kbit/s", "8 kbit/s", "Reserved"}[config.datarate]); + PrintAndLogEx(INFO, " Encoding.......... %s", config.encoding ? _YELLOW_("Bi-phase") : _YELLOW_("Manchester")); + PrintAndLogEx(INFO, " Password Protect W Bit 0-127(block 0-3) %s", config.pwdW0_127 ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect W Bit 128-511(block 4-15) %s", config.pwdW128_511 ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect W Bit 512-Max(block 16-Max) %s", config.pwdW512_max ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect RW Bit 512-Max(block 16-Max) %s", config.pwdRW512_max ? _RED_("Yes") : _GREEN_("No")); +} + +static void hitagu8265_config_print(hitagu82xx_config_t config) { + PrintAndLogEx(INFO, " Config Byte0: %s", sprint_hex((uint8_t *)&config, sizeof(config))); // for debug + // Check if datarate_override is set + if (config.datarate_override) { + PrintAndLogEx(INFO, " Data Rate........ %s", _YELLOW_("2 kbit/s")); + } else { + PrintAndLogEx(INFO, " Data Rate........ %s", + (const char *[]) {"2 kbit/s", "4 kbit/s", "8 kbit/s", "2 kbit/s"}[config.datarate]); + } + PrintAndLogEx(INFO, " Rate Override.... %s", config.datarate_override ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Encoding......... %s", config.encoding ? _YELLOW_("Bi-phase") : _YELLOW_("Manchester")); + + PrintAndLogEx(INFO, " TTF mode ........ %s", + (const char *[]) { + "Block 0, Block 1, Block 2, Block 3", + "Block 0, Block 1", + "Block 0, Block 1, Block 2, Block 3", + "Block 0, Block 1, Block 2, Block 3", + }[config.ttf_mode]); + PrintAndLogEx(INFO, " TTF.............. %s", config.ttf ? _GREEN_("Enabled") : _RED_("Disabled")); +} + +static int CmdLFHitagURead(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu rdbl", + "Read Hitag µ memory.\n\n" + " 82xx password mode: \n" + " - default password 00000000\n", + " lf hitag htu rdbl -p 1 -> Hitag µ, plain mode\n" + " lf hitag htu rdbl -p 1 --82xx -> 82xx, password mode, def pass\n" + " lf hitag htu rdbl -p 1 --82xx -k 9AC4999C -> 82xx, password mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_int0("p", "page", "", "block address to read from (def: 0)"), + arg_int0("c", "count", "", "how many blocks to read. '0' reads all blocks (def: 1)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + lf_hitag_data_t packet; + + if (process_hitagu_common_args(ctx, &packet) < 0) return PM3_EINVARG; + + uint32_t page = arg_get_int_def(ctx, 3, 0); + + if (page >= HITAGU_MAX_BLOCKS) { + PrintAndLogEx(WARNING, "Block address Invalid. Maximum is 255."); + return PM3_EINVARG; + } + + uint32_t count = arg_get_int_def(ctx, 4, 1); + + if (count > HITAGU_MAX_BLOCKS) { + PrintAndLogEx(WARNING, "No more than %d blocks can be read at once.", HITAGU_MAX_BLOCKS); + return PM3_EINVARG; + } + + CLIParserFree(ctx); + + packet.page = page; + packet.page_count = count; + // packet.mode = 1; // for debug + + PrintAndLogEx(INFO, "Read Hitag µ memory block %d, count %d", page, count); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return PM3_ESOFT; + } + + lf_htu_read_response_t *card = (lf_htu_read_response_t *)resp.data.asBytes; + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + + int user_blocks; + uint8_t icr = card->icr; + + if (icr == HITAGU_ICR_STANDARD) { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED; + PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; + PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_8265) { + user_blocks = HITAGU_MAX_PAGE_8265; + PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); + } + + if (packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) { + hitagu8265_config_print(card->config_page.s82xx); + } else { + hitagu_config_print(card->config_page.s); + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Data") " ---------------------------"); + PrintAndLogEx(INFO, " # | 00 01 02 03 | ascii | perm | info"); + PrintAndLogEx(INFO, "----+-------------+-------+------+------"); + + if (count == 0) { + count = (user_blocks > page) ? (user_blocks - page) : HITAGU_MAX_BLOCKS; + } + + for (int i = 0; i < count; ++i) { + int page_addr = page + i; + if (page_addr >= HITAGU_MAX_BLOCKS) { + break; + } + if (card->pages_reason[i] > 0) { + PrintAndLogEx(SUCCESS, "% 3u | %s | " NOLF, page_addr, sprint_hex_ascii(card->pages[i], HITAGU_BLOCK_SIZE)); + + // access right + // 82xx + if ((packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) && page_addr != HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, _YELLOW_("RO ") NOLF); + } else if ((packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) && page_addr == HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); + // Hitag µ + } else if (page_addr < HITAGU_MAX_PAGE_STANDARD) { + if (card->config_page.s.pwdW0_127) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } else if (HITAGU_MAX_PAGE_STANDARD <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED) { + if (card->config_page.s.pwdW128_511) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } else if (HITAGU_MAX_PAGE_ADVANCED <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED_PLUS) { + if (card->config_page.s.pwdRW512_max) { + PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); + } else { + if (card->config_page.s.pwdW512_max) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } + } else + PrintAndLogEx(NORMAL, _YELLOW_("UNK ") NOLF); + + PrintAndLogEx(NORMAL, " | " NOLF); + + // info + if (page_addr == HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, "Password"); + } else if (page_addr == HITAGU_CONFIG_PADR) { + PrintAndLogEx(NORMAL, "Config"); + } else + PrintAndLogEx(NORMAL, "Data"); + } else { + PrintAndLogEx(INFO, "% 3u | -- -- -- -- | .... | N/A | " NOLF, page_addr); + print_error(card->pages_reason[i]); + } + } + + PrintAndLogEx(INFO, "----+-------------+-------+------+------"); + PrintAndLogEx(INFO, " " _YELLOW_("RO") " = Read without password, write with password"); + PrintAndLogEx(INFO, " " _GREEN_("R/W") " = Read and write without password"); + PrintAndLogEx(INFO, " " _RED_("R/WP") " = Read and write with password"); + PrintAndLogEx(INFO, "----------------------------------------"); + return PM3_SUCCESS; +} + +static int CmdLFHitagUDump(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu dump", + "Read all Hitag µ memory and save to file\n" + " 82xx password mode: \n" + " - default password 00000000\n", + "lf hitag htu dump --82xx -> use def pwd\n" + "lf hitag htu dump --82xx -k 9AC4999C -> pwd mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_str0("f", "file", "", "specify file name"), + arg_lit0(NULL, "ns", "no save to file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + lf_hitag_data_t packet; + memset(&packet, 0, sizeof(packet)); + + if (process_hitagu_common_args(ctx, &packet) < 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + + bool nosave = arg_get_lit(ctx, 4); + CLIParserFree(ctx); + + // read all pages + packet.page = 0; + packet.page_count = 0; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 5000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return PM3_ESOFT; + } + + lf_htu_read_response_t *card = (lf_htu_read_response_t *)resp.data.asBytes; + + int user_blocks; + uint8_t icr = card->icr; + + if (icr == HITAGU_ICR_STANDARD) { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED; + PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; + PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_8265) { + user_blocks = HITAGU_MAX_PAGE_8265; + PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); + } + + int mem_size = (user_blocks + 2) * HITAGU_BLOCK_SIZE; + + hitagu_config_t config = card->config_page.s; + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + hitagu_config_print(config); + + if (nosave) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Called with no save option"); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; + } + + if (fnlen < 1) { + char *fptr = filename; + fptr += snprintf(filename, sizeof(filename), "lf-hitagu-"); + FillFileNameByUID(fptr, card->uid, "-dump", HITAGU_UID_SIZE); + } + + pm3_save_dump(filename, (uint8_t *)card->pages, mem_size, jsfHitag); + + return PM3_SUCCESS; +} + +static int CmdLFHitagUWrite(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu wrbl", + "Write a block in Hitag µ memory.\n" + " 82xx password mode: \n" + " - default password 00000000\n", + " lf hitag htu wrbl -p 6 -d 01020304 -> Hitag µ, plain mode\n" + " lf hitag htu wrbl -p 6 -d 01020304 --82xx -> use def pwd\n" + " lf hitag htu wrbl -p 6 -d 01020304 --82xx -k 9AC4999C -> 82xx, password mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_int1("p", "page", "", "block address to write to"), + arg_str1("d", "data", "", "data, 4 hex bytes"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + lf_hitag_data_t packet; + + if (process_hitagu_common_args(ctx, &packet) < 0) return PM3_EINVARG; + + int page = arg_get_int_def(ctx, 3, 0); + + uint8_t data[HITAGU_BLOCK_SIZE]; + int data_len = 0; + + int res = CLIParamHexToBuf(arg_get_str(ctx, 4), data, HITAGU_BLOCK_SIZE, &data_len); + if (res != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + CLIParserFree(ctx); + + packet.page = page; + memcpy(packet.data, data, sizeof(packet.data)); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_WRITE, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_WRITE, &resp, 4000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_ETEAROFF) { + PrintAndLogEx(INFO, "Writing tear off triggered"); + return PM3_SUCCESS; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return resp.status; + } + + PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )"); + return PM3_SUCCESS; +} + +static int CmdLFHitagUReader(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu reader", "Act as a Hitag µ reader. Look for Hitag µ tags until Enter or the pm3 button is pressed\n", + "lf hitag htu reader\n" + "lf hitag htu reader -@ -> Continuous mode"); + + void *argtable[] = {arg_param_begin, arg_lit0("@", NULL, "continuous reader mode"), arg_param_end}; + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool cm = arg_get_lit(ctx, 1); + CLIParserFree(ctx); + + if (cm) { + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + } + + do { + // read UID + read_htu_uid(); + } while (cm && kbd_enter_pressed() == false); + + return PM3_SUCCESS; +} + +static int CmdLFHitagUSim(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu sim", + "Simulate Hitag µ transponder\n" + "You need to `lf hitag htu eload` first", + "lf hitag htu sim\n" + "lf hitag htu sim --82xx\n" + "lf hitag htu sim -t 30 -> set threshold to 30"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("8", "82xx", "simulate 82xx"), + arg_int0("t", "threshold", "", "set edge detect threshold (def: 127)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + // bool use_82xx = arg_get_lit(ctx, 1); // not implemented yet + int threshold = arg_get_int_def(ctx, 2, 127); + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandMIX(CMD_LF_HITAGU_SIMULATE, false, threshold, 0, NULL, 0); + return PM3_SUCCESS; +} + +static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "hitagu"); } + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help" }, + {"list", CmdLFHitagUList, AlwaysAvailable, "List Hitag µ trace history" }, + {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("General") " ------------------------" }, + {"reader", CmdLFHitagUReader, IfPm3Hitag, "Act like a Hitag µ reader" }, + {"rdbl", CmdLFHitagURead, IfPm3Hitag, "Read Hitag µ block" }, + {"dump", CmdLFHitagUDump, IfPm3Hitag, "Dump Hitag µ blocks to a file" }, + {"wrbl", CmdLFHitagUWrite, IfPm3Hitag, "Write Hitag µ block" }, + {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Simulation") " -----------------------"}, + {"sim", CmdLFHitagUSim, IfPm3Hitag, "Simulate Hitag µ transponder" }, + {NULL, NULL, 0, NULL } +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdLFHitagU(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdlfhitagu.h b/client/src/cmdlfhitagu.h new file mode 100644 index 000000000..af2153083 --- /dev/null +++ b/client/src/cmdlfhitagu.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency Hitag µ support +//----------------------------------------------------------------------------- + +#ifndef CMDLFHITAGU_H__ +#define CMDLFHITAGU_H__ + +#include "common.h" +#include "hitag.h" + +uint8_t hitagu_CRC_check(uint8_t *d, uint32_t nbit); +void annotateHitagU(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response); + +int CmdLFHitagU(const char *Cmd); + +int read_htu_uid(void); + +#endif //CMDLFHITAGU_H__ diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 1d0344258..d563d4478 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -29,6 +29,7 @@ #include "fileutils.h" // for saveFile #include "cmdlfhitag.h" // annotate hitag #include "cmdlfhitaghts.h" // annotate hitags +#include "cmdlfhitagu.h" // annotate hitagu #include "pm3_cmd.h" // tracelog_hdr_t #include "cliparser.h" // args.. @@ -586,8 +587,12 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case PROTO_HITAG1: case PROTO_HITAGS: crcStatus = hitag1_CRC_check(frame, (data_len * 8) - ((8 - parityBytes[0]) % 8)); - case PROTO_CRYPTORF: + break; + case PROTO_HITAGU: + crcStatus = hitagu_CRC_check(frame, (data_len * 8) - ((8 - parityBytes[0]) % 8)); + break; case PROTO_HITAG2: + case PROTO_CRYPTORF: default: break; } @@ -625,6 +630,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr && protocol != PROTO_HITAG1 && protocol != PROTO_HITAG2 && protocol != PROTO_HITAGS + && protocol != PROTO_HITAGU && protocol != THINFILM && protocol != FELICA && protocol != LTO @@ -647,7 +653,7 @@ 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))) { + } else if (((protocol == PROTO_HITAG1) || (protocol == PROTO_HITAG2) || (protocol == PROTO_HITAGS) || (protocol == PROTO_HITAGU))) { if (j == 0) { @@ -803,6 +809,9 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case PROTO_HITAGS: annotateHitagS(explanation, sizeof(explanation), frame, (data_len * 8) - ((8 - parityBytes[0]) % 8), hdr->isResponse); break; + case PROTO_HITAGU: + annotateHitagU(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); + break; case ICLASS: annotateIclass(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); break; @@ -1314,6 +1323,7 @@ int CmdTraceList(const char *Cmd) { "trace list -t hitag1 -> interpret as " _YELLOW_("Hitag 1") "\n" "trace list -t hitag2 -> interpret as " _YELLOW_("Hitag 2") "\n" "trace list -t hitags -> interpret as " _YELLOW_("Hitag S") "\n" + "trace list -t hitagu -> interpret as " _YELLOW_("Hitag µ") "\n" "trace list -t iclass -> interpret as " _YELLOW_("iCLASS") "\n" "trace list -t legic -> interpret as " _YELLOW_("LEGIC") "\n" "trace list -t lto -> interpret as " _YELLOW_("LTO-CM") "\n" @@ -1381,6 +1391,7 @@ int CmdTraceList(const char *Cmd) { else if (strcmp(type, "hitag1") == 0) protocol = PROTO_HITAG1; else if (strcmp(type, "hitag2") == 0) protocol = PROTO_HITAG2; else if (strcmp(type, "hitags") == 0) protocol = PROTO_HITAGS; + else if (strcmp(type, "hitagu") == 0) protocol = PROTO_HITAGU; else if (strcmp(type, "iclass") == 0) protocol = ICLASS; else if (strcmp(type, "legic") == 0) protocol = LEGIC; else if (strcmp(type, "lto") == 0) protocol = LTO; @@ -1470,8 +1481,8 @@ 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) { - PrintAndLogEx(INFO, _YELLOW_("Hitag 1 / Hitag 2 / Hitag S") " - Timings in ETU (8us)"); + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS || protocol == PROTO_HITAGU) { + PrintAndLogEx(INFO, _YELLOW_("Hitag 1 / Hitag 2 / Hitag S / Hitag µ") " - Timings in ETU (8us)"); } if (protocol == PROTO_FMCOS20) { @@ -1552,7 +1563,7 @@ int CmdTraceList(const char *Cmd) { } // reset hitag state machine - if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) { + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS || protocol == PROTO_HITAGU) { annotateHitag2_init(); } diff --git a/include/hitag.h b/include/hitag.h index 330ce8ddf..a8e64d452 100644 --- a/include/hitag.h +++ b/include/hitag.h @@ -13,7 +13,7 @@ // // See LICENSE.txt for the text of the license. //----------------------------------------------------------------------------- -// Hitag 2, Hitag S +// Hitag 2, Hitag S, Hitag µ //----------------------------------------------------------------------------- @@ -39,6 +39,26 @@ #define HITAGS_UID_PADR 0 #define HITAGS_CONFIG_PADR 1 +// Add Hitag µ specific definitions +#define HITAGU_UID_SIZE 6 +#define HITAGU_BLOCK_SIZE HITAG_BLOCK_SIZE +#define HITAGU_MAX_BLOCKS 0x100 +#define HITAGU_MAX_BYTE_SIZE (HITAGU_MAX_BLOCKS * HITAGU_BLOCK_SIZE) +#define HITAGU_CONFIG_PADR 0xFF +#define HITAGU_PASSWORD_PADR 0xFE + +// Hitag μ IC Revision (ICR) values +#define HITAGU_ICR_STANDARD 0x10 // Standard Hitag μ +#define HITAGU_ICR_ADVANCED 0x20 // Hitag μ advanced +#define HITAGU_ICR_ADVANCED_PLUS 0x30 // Hitag μ advanced+ +#define HITAGU_ICR_8265 0x80 // 8265 + +// Hitag μ memory sizes based on ICR +#define HITAGU_MAX_PAGE_STANDARD 0x04 // 4 blocks (0x00-0x03) for standard Hitag μ +#define HITAGU_MAX_PAGE_ADVANCED 0x10 // 16 blocks (0x00-0x0F) for Hitag μ advanced +#define HITAGU_MAX_PAGE_ADVANCED_PLUS 0x37 // 56 blocks (0x00-0x36) for Hitag μ advanced+ +#define HITAGU_MAX_PAGE_8265 0x0F // 15 blocks (0x00-0x0E) for 8265 + // need to see which limits these cards has #define HITAG1_MAX_BYTE_SIZE 64 #define HITAG_MAX_BYTE_SIZE (64 * HITAG_BLOCK_SIZE) @@ -58,18 +78,24 @@ typedef enum { HTSF_82xx, HTSF_CHALLENGE, HTSF_KEY, - HTS_LAST_CMD = HTSF_KEY, + HTS_LAST_CMD = HTSF_KEY, HT1F_PLAIN, HT1F_AUTHENTICATE, - HT1_LAST_CMD = HT1F_AUTHENTICATE, + HT1_LAST_CMD = HT1F_AUTHENTICATE, HT2F_PASSWORD, HT2F_AUTHENTICATE, HT2F_CRYPTO, HT2F_TEST_AUTH_ATTEMPTS, HT2F_UID_ONLY, - HT2_LAST_CMD = HT2F_UID_ONLY, + HT2_LAST_CMD = HT2F_UID_ONLY, + + // Add Hitag µ commands + HTUF_PLAIN, + HTUF_82xx, + HTUF_PASSWORD, + HTU_LAST_CMD = HTUF_PASSWORD, } PACKED hitag_function; //--------------------------------------------------------- @@ -150,6 +176,57 @@ struct hitagS_tag { } PACKED; +// Configuration byte 0 bit definitions +#define HITAGU_BYTE0_DATARATE_MASK 0x03 // Bits 0-1: data rate +#define HITAGU_BYTE0_DATARATE_2K 0x00 // 00 = 2kbit/s +#define HITAGU_BYTE0_DATARATE_4K 0x01 // 01 = 4kbit/s +#define HITAGU_BYTE0_DATARATE_8K 0x02 // 10 = 8kbit/s +#define HITAGU_BYTE0_ENCODING_MASK 0x04 // Bit 2: encoding +#define HITAGU_BYTE0_ENCODING_MANCHESTER 0x00 // 0 = Manchester +#define HITAGU_BYTE0_ENCODING_BIPHASE 0x01 // 1 = Biphase + +// Hitag µ configuration structure +typedef struct { + // byte0 + uint8_t datarate: 2; + uint8_t encoding: 1; + uint8_t pwdW0_127: 1; + uint8_t pwdW128_511: 1; + uint8_t pwdW512_max: 1; + uint8_t pwdRW512_max: 1; +} PACKED hitagu_config_t; + +typedef struct { + // byte0 + uint8_t datarate : 2; // 00 = 2kbit/s, 01 = 4kbit/s, 10 = 8kbit/s, 11 = 2kbit/s + uint8_t datarate_override : 1; // 0 = datarate, 1 = 2kbit/s + uint8_t encoding : 1; // 0 = Manchester, 1 = Biphase + + uint8_t reserved : 1; + uint8_t ttf_mode : 2; // 00/10/11 = "Block 0, Block 1, Block 2, Block 3", 01 = "Block 0, Block 1" + uint8_t ttf : 1; +} PACKED hitagu82xx_config_t; + +// Hitag µ tag structure +struct hitagU_tag { + PSTATE pstate; // protocol-state + TSATE tstate; // tag-state + + int max_page; + uint8_t uid[HITAGU_UID_SIZE]; + union { + uint8_t asBytes[HITAGU_BLOCK_SIZE]; + hitagu_config_t s; + hitagu82xx_config_t s82xx; + } config; + uint8_t password[HITAG_PASSWORD_SIZE]; + uint8_t icr; // IC Revision value - determines memory size + + union { + uint8_t pages[HITAGU_MAX_BLOCKS][HITAGU_BLOCK_SIZE]; + } data; +} PACKED; + typedef struct { hitag_function cmd; uint8_t page; @@ -170,6 +247,9 @@ typedef struct { // Hitag S section uint8_t mode; + + // Hitag µ section + uint8_t uid[HITAGU_UID_SIZE]; } PACKED lf_hitag_data_t; typedef struct { @@ -185,4 +265,18 @@ typedef struct { int8_t pages_reason[HITAGS_MAX_PAGES]; uint8_t pages[HITAGS_MAX_PAGES][HITAGS_PAGE_SIZE]; } PACKED lf_hts_read_response_t; + +// Hitag µ read response structure +typedef struct { + union { + uint8_t asBytes[HITAGU_BLOCK_SIZE]; + hitagu_config_t s; + hitagu82xx_config_t s82xx; + } config_page; + uint8_t uid[HITAGU_UID_SIZE]; + uint8_t icr; // IC Revision value for memory size detection + int8_t pages_reason[HITAGU_MAX_PAGE_ADVANCED_PLUS]; + uint8_t pages[HITAGU_MAX_PAGE_ADVANCED_PLUS][HITAGU_BLOCK_SIZE]; +} PACKED lf_htu_read_response_t; + #endif diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index a71e09a1e..d23f00870 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -282,7 +282,7 @@ typedef struct { typedef struct { // 64KB SRAM -> 524288 bits(max sample num) < 2^30 -uint32_t samples : + uint32_t samples : LF_SAMPLES_BITS; bool realtime : 1; bool verbose : 1; @@ -602,6 +602,12 @@ typedef struct { #define CMD_LF_HITAGS_WRITE 0x0375 #define CMD_LF_HITAGS_UID 0x037A +// For Hitag µ +#define CMD_LF_HITAGU_READ 0x037B +#define CMD_LF_HITAGU_WRITE 0x037C +#define CMD_LF_HITAGU_SIMULATE 0x037D +#define CMD_LF_HITAGU_UID 0x037E + #define CMD_LF_HITAG_ELOAD 0x0376 #define CMD_HF_ISO14443A_ANTIFUZZ 0x0380 diff --git a/include/protocols.h b/include/protocols.h index 7048263d5..70795fcd0 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -455,12 +455,14 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define LTO 12 #define PROTO_HITAG2 13 #define PROTO_HITAGS 14 -#define PROTO_CRYPTORF 15 -#define SEOS 16 -#define PROTO_MFPLUS 17 -#define PROTO_TEXKOM 18 -#define PROTO_XEROX 19 -#define PROTO_FMCOS20 20 +#define PROTO_HITAGU 15 +#define PROTO_CRYPTORF 16 +#define SEOS 17 +#define PROTO_MFPLUS 18 +#define PROTO_TEXKOM 19 +#define PROTO_XEROX 20 +#define PROTO_FMCOS20 21 +#define COUNT_OF_PROTOCOLS 22 // Picopass fuses #define FUSE_FPERS 0x80 @@ -948,6 +950,25 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define HITAGS_WRITE_BLOCK 0x90 // 1001 WRITE BLOCK #define HITAGS_QUIET 0x70 // 0111 QUIET +// Hitag μ flags +#define HITAGU_FLAG_PEXT 0x01 // 0b00001 - Protocol EXTension flag +#define HITAGU_FLAG_INV 0x02 // 0b00010 - INVentory flag +#define HITAGU_FLAG_CRCT 0x04 // 0b00100 - CRC Transponder flag +#define HITAGU_FLAG_SEL 0x08 // 0b01000 - SELect flag (when INV=0) +#define HITAGU_FLAG_ADR 0x10 // 0b10000 - ADdRess flag (when INV=0) +#define HITAGU_FLAG_RFU 0x08 // 0b01000 - Reserved For Use flag (when INV=1, always 0) +#define HITAGU_FLAG_NOS 0x10 // 0b10000 - Number Of Slots flag (when INV=1) + +// Hitag μ commands (6-bit) +#define HITAGU_CMD_LOGIN 0x28 // 0b101000 - Login command +#define HITAGU_CMD_INVENTORY 0x00 // 0b000000 - Inventory command +#define HITAGU_CMD_READ_MULTIPLE_BLOCK 0x12 // 0b010010 - Read multiple block command +#define HITAGU_CMD_WRITE_SINGLE_BLOCK 0x14 // 0b010100 - Write single block command +#define HITAGU_CMD_SELECT 0x18 // 0b011000 - Select command +#define HITAGU_CMD_SYSINFO 0x17 // 0b010111 - Get system information command +#define HITAGU_CMD_READ_UID 0x02 // 0b000010 - Read UID command +#define HITAGU_CMD_STAY_QUIET 0x01 // 0b000001 - Stay quiet command + // LTO-CM commands #define LTO_REQ_STANDARD 0x45 #define LTO_REQ_ALL 0x4A From 04133e6d078856bd7e4ec2fa053bca01d84cae6b Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:19:58 +0800 Subject: [PATCH 27/43] =?UTF-8?q?Add=20`lf=20search`=20Chipset=20detection?= =?UTF-8?q?=20for=20Hitag=20=C2=B5=20/=208265?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/cmdlf.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index b2f8ba20c..7bf4dd553 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -41,6 +41,7 @@ #include "cmdlfhid.h" // for hid menu #include "cmdlfhitag.h" // for hitag menu #include "cmdlfhitaghts.h" // for hitag S sub commands +#include "cmdlfhitagu.h" // for hitag µ sub commands #include "cmdlfidteck.h" // for idteck menu #include "cmdlfio.h" // for ioprox menu #include "cmdlfcotag.h" // for COTAG menu @@ -1602,6 +1603,14 @@ static bool check_chiptype(bool getDeviceData) { retval = true; goto out; } + + // Hitag µ + if (read_htu_uid() == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Chipset detection: " _GREEN_("Hitag µ / 8265")); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf hitag htu`") " commands"); + retval = true; + goto out; + } } From 2ff92c3b5c4670eb844d17ef0a7158d96bfa5999 Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:21:43 +0800 Subject: [PATCH 28/43] =?UTF-8?q?add=20`lf=20em=20410x=20clone=20--htu`=20?= =?UTF-8?q?to=20support=20Hitag=20=C2=B5/8265=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/cmdlfem410x.c | 146 ++++++++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 33 deletions(-) diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index 7f296336c..3d4d5f3f2 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -624,11 +624,12 @@ static int CmdEM410xSpoof(const char *Cmd) { static int CmdEM410xClone(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 410x clone", - "clone a EM410x ID to a T55x7, Q5/T5555, EM4305/4469 or Hitag S/8211/8268/8310 tag.", + "clone a EM410x ID to a T55x7, Q5/T5555, EM4305/4469, Hitag S/8211/8268/8310 or Hitag µ/8265 tag.", "lf em 410x clone --id 0F0368568B -> encode for T55x7 tag\n" "lf em 410x clone --id 0F0368568B --q5 -> encode for Q5/T5555 tag\n" "lf em 410x clone --id 0F0368568B --em -> encode for EM4305/4469\n" - "lf em 410x clone --id 0F0368568B --hts -> encode for Hitag S/8211/8268/8310" + "lf em 410x clone --id 0F0368568B --hts -> encode for Hitag S/8211/8268/8310\n" + "lf em 410x clone --id 0F0368568B --htu -> encode for Hitag µ/8265 tag" ); void *argtable[] = { @@ -638,6 +639,7 @@ static int CmdEM410xClone(const char *Cmd) { arg_lit0(NULL, "q5", "optional - specify writing to Q5/T5555 tag"), arg_lit0(NULL, "em", "optional - specify writing to EM4305/4469 tag"), arg_lit0(NULL, "hts", "optional - specify writing to Hitag S/8211/8268/8310 tag"), + arg_lit0(NULL, "htu", "optional - specify writing to Hitag µ/8265 tag"), arg_lit0(NULL, "electra", "optional - add Electra blocks to tag"), arg_param_end }; @@ -651,23 +653,23 @@ static int CmdEM410xClone(const char *Cmd) { bool q5 = arg_get_lit(ctx, 3); bool em = arg_get_lit(ctx, 4); bool hts = arg_get_lit(ctx, 5); - bool add_electra = arg_get_lit(ctx, 6); + bool htu = arg_get_lit(ctx, 6); + bool add_electra = arg_get_lit(ctx, 7); CLIParserFree(ctx); - if (q5 + em + hts > 1) { + if (q5 + em + hts + htu > 1) { PrintAndLogEx(FAILED, "Only specify one tag Type"); return PM3_EINVARG; } - if (hts) { - if (IfPm3Hitag() == false) { - PrintAndLogEx(FAILED, "Device not compiled to support Hitag"); - return PM3_EINVARG; - } - if (clk == 40) { - PrintAndLogEx(FAILED, "supported clock rates for Hitag are " _YELLOW_("16, 32, 64")); - return PM3_EINVARG; - } + if ((hts || htu) && IfPm3Hitag() == false) { + PrintAndLogEx(FAILED, "Device not compiled to support Hitag"); + return PM3_EINVARG; + } + + if ((hts || htu) && clk == 40) { + PrintAndLogEx(FAILED, "supported clock rates for Hitag are " _YELLOW_("16, 32, 64")); + return PM3_EINVARG; } // Allowed clock rates: 16, 32, 40 and 64 @@ -678,9 +680,9 @@ static int CmdEM410xClone(const char *Cmd) { uint64_t id = bytes_to_num(uid, uid_len); PrintAndLogEx(SUCCESS, "Preparing to clone EM4102 to " _YELLOW_("%s") " tag with EM Tag ID " _GREEN_("%010" PRIX64) " (RF/%d)", - q5 ? "Q5/T5555" : (em ? "EM4305/4469" : (hts ? "Hitag S/82xx" : "T55x7")), id, clk); + q5 ? "Q5/T5555" : (em ? "EM4305/4469" : (hts ? "Hitag S/82xx" : (htu ? "Hitag µ/82xx" : "T55x7"))), id, clk); - uint8_t data[HITAG_BLOCK_SIZE * 2] = {0xFF, 0x80}; // EM410X_HEADER 9 bits of one + uint8_t data[8] = {0xFF, 0x80}; // EM410X_HEADER 9 bits of one uint32_t databits = 9; uint8_t c_parity = 0; @@ -706,35 +708,45 @@ static int CmdEM410xClone(const char *Cmd) { lf_hitag_data_t packet; memset(&packet, 0, sizeof(packet)); - for (size_t steps = 0; steps < 3; steps++) { - switch (steps) { - case 0: - packet.data[0] = 0xCA; //compatiable for 82xx, no impact on Hitag S - // clk -> TTFDR1 TTFDR0 - // 32 -> 0x00 4 kBit/s - // 16 -> 0x10 8 kBit/s - // 64 -> 0x20 2 kBit/s - packet.data[1] = 0x04; + for (size_t step = 0; step < 3; step++) { + switch (step) { + case 0: { + hitags_config_t config = {0}; + config.MEMT = 0x02; // compatiable for 82xx, no impact on Hitag S + config.TTFM = 0x01; // 0 = "Block 0, Block 1, Block 2, Block 3", 1 = "Block 0, Block 1" + config.TTFC = 0x00; // Manchester + config.auth = 0x00; // Plain + + //compatiable for 82xx, no impact on Hitag S + config.RES1 = 0x01; + config.RES4 = 0x01; + config.RES5 = 0x01; switch (clk) { + case 64: + // 2 kBit/s + config.TTFDR = 0x02; + break; case 32: + // 4 kBit/s + config.TTFDR = 0x00; break; case 16: - packet.data[1] |= 0x10; - break; - case 64: - packet.data[1] |= 0x20; + // 8 kBit/s + config.TTFDR = 0x01; break; } - packet.data[2] = 0; - packet.data[3] = 0; //TODO: keep PWDH0? + //TODO: keep other fields? + memcpy(packet.data, &config, sizeof(config)); + // PrintAndLogEx(INFO, "packet.data: %s", sprint_hex(packet.data, sizeof(packet.data))); packet.page = 1; break; + } case 1: - memcpy(packet.data, &data[HITAG_BLOCK_SIZE * 0], HITAG_BLOCK_SIZE); + memcpy(packet.data, &data[HITAGS_PAGE_SIZE * 0], HITAGS_PAGE_SIZE); packet.page = 4; break; case 2: - memcpy(packet.data, &data[HITAG_BLOCK_SIZE * 1], HITAG_BLOCK_SIZE); + memcpy(packet.data, &data[HITAGS_PAGE_SIZE * 1], HITAGS_PAGE_SIZE); packet.page = 5; break; } @@ -748,10 +760,78 @@ static int CmdEM410xClone(const char *Cmd) { return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { - PrintAndLogEx(WARNING, "Something went wrong"); + PrintAndLogEx(WARNING, "Something went wrong in step %zu", step); return resp.status; } } + } else if (htu) { + lf_hitag_data_t packet; + memset(&packet, 0, sizeof(packet)); + + // Use password auth with default password + packet.cmd = HTUF_82xx; + memcpy(packet.pwd, "\x00\x00\x00\x00", HITAG_PASSWORD_SIZE); + // memcpy(packet.pwd, "\x9A\xC4\x99\x9C", HITAGU_BLOCK_SIZE); + + for (size_t step = 0; step < 3; step++) { + switch (step) { + case 0: { + // Configure datarate based on clock + // clk -> datarate + // 64 -> 0x00 2 kBit/s + // 32 -> 0x01 4 kBit/s + // 16 -> 0x10 8 kBit/s + hitagu82xx_config_t config = {0}; + + config.datarate_override = 0x00; // no datarate override + config.encoding = 0x00; // Manchester + config.ttf_mode = 0x01; // 01 = "Block 0, Block 1" + config.ttf = 0x01; // enable TTF + + switch (clk) { + case 64: + break; + case 32: + config.datarate = 0x01; + break; + case 16: + config.datarate = 0x02; + break; + } + packet.data[0] = reflect8(*(uint8_t*)&config); + packet.page = HITAGU_CONFIG_PADR; // Config block + break; + } + case 1: + memcpy(packet.data, &data[HITAGU_BLOCK_SIZE * 0], HITAGU_BLOCK_SIZE); + packet.page = 0; // Start writing EM410x data + break; + case 2: + memcpy(packet.data, &data[HITAGU_BLOCK_SIZE * 1], HITAGU_BLOCK_SIZE); + packet.page = 1; // Continue with second block + break; + } + + SendCommandNG(CMD_LF_HITAGU_WRITE, (uint8_t *)&packet, sizeof(packet)); + if (WaitForResponseTimeout(CMD_LF_HITAGU_WRITE, &resp, 4000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_ENODATA && resp.status != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Something went wrong in step %zu, retrying... Press " _GREEN_("") " to exit", step); + // 8265 Often fails during continuous command execution, need to retry + if (kbd_enter_pressed()) { + PrintAndLogEx(INFO, "Button pressed, user aborted"); + return PM3_EOPABORTED; + } + + step--; + continue; + } + //TODO: fix this + resp.status = PM3_SUCCESS; + } } else { struct { bool Q5; From 7729786e78eebadd7e2d8fd2c32b5db803d867f1 Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Sat, 15 Mar 2025 04:22:43 +0800 Subject: [PATCH 29/43] =?UTF-8?q?Update=20CHANGELOG.md=20and=20doc/magic?= =?UTF-8?q?=5Fcards=5Fnotes.md=20for=20Hitag=20=C2=B5/8265?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 ++ doc/magic_cards_notes.md | 16 +++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1036dd33..16c7021e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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 em 410x clone --htu` clone EM410x ID to Hitag µ/8265 (@douniwan5788) +- Added `lf hitag htu` support for Hitag µ/8265 (@douniwan5788) - Added `hf mfu aesauth` based on existing UL AES support (@doegox) - Changed `hf mfu sim` deny OTP changes with all zeros (@iceman1001) - Added missing file in CMakeLists.txt (@iceman1001) diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index e103b1188..9c477a69a 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -158,11 +158,17 @@ This is the cheapest and most common ID82xx chip available. It is usually sold a * Chip is likely a cut down version of Hitag μ (micro) clone * UID `00 00 00 00 00 00` * Password protection (4b), usually "00000000"(default) or "9AC4999C"(FURUI) -* CON0 - * bit 0-1 -> data rate ’00’... 2kbit/s ’01’... 4kbit/s ’10’... 8kbit/s ’11’... 2kbit/s - * bit 2 when set, fixed to MC 2kbit/s - * bit 3-6 reversed? all blocks always read without password and write with password - * bit 7 -> enable TTF +* Config block 0xFF + * Byte0 + * bit 0-1 : Data Rate. ’00’ -> 2kbit/s, ’01’ -> 4kbit/s, ’10’ -> 8kbit/s, ’11’ -> 2kbit/s + * bit 2 : 1 -> fixed to 2kbit/s + * bit 3 : 0 -> Manchester, 1 -> Bi-phase + * bit 4 : TTF blocks. 0 -> "Block 0, Block 1, Block 2, Block 3", 1 -> "Block 0, Block 1" + * bit 5-6 : reversed? all blocks always read without password and write with password + * bit 7 : 1 -> enable TTF + * Byte1 only bit 0 changable + * Byte2 fixed 0x00 + * Byte3 only higher nibble changable * Currently unimplemented in proxmark3 client * Other names: * ID8210 (CN) From ecdfa9755e5d3429fbcfcee9a334c1750cbbd0b1 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 19 Mar 2025 12:03:35 +0100 Subject: [PATCH 30/43] MifareUL_AES_Auth: do not let HF field on when erroring --- armsrc/mifarecmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index a2aa2450d..3fbd807f7 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -294,13 +294,13 @@ void MifareUL_AES_Auth(bool turn_off_field, uint8_t keyno, uint8_t *keybytes) { if (!iso14443a_select_card(NULL, NULL, NULL, true, 0, true)) { if (g_dbglevel >= DBG_ERROR) Dbprintf("Can't select card"); - reply_ng(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT, NULL, 0); + OnErrorNG(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT); return; }; if (!mifare_ultra_aes_auth(keyno, keybytes)) { if (g_dbglevel >= DBG_ERROR) Dbprintf("Authentication failed"); - reply_ng(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT, NULL, 0); + OnErrorNG(CMD_HF_MIFAREULAES_AUTH, PM3_ESOFT); return; } From 214ded2b971982edc240a46fa80f7c89dd32899c Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Wed, 19 Mar 2025 19:21:01 +0800 Subject: [PATCH 31/43] =?UTF-8?q?Replace=20all=20=CE=BC(greek=20small=20le?= =?UTF-8?q?tter=20mu,=20UTF-8=20\xce\xbc)=20to=20=C2=B5(micro=20sign,=20UT?= =?UTF-8?q?F-8=20\xc2\xb5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- armsrc/felica.c | 4 ++-- armsrc/hitagu.c | 10 +++++----- armsrc/lfops.c | 8 ++++---- client/src/cmdlfhitagu.c | 16 ++++++++-------- doc/magic_cards_notes.md | 2 +- include/hitag.h | 16 ++++++++-------- include/protocols.h | 4 ++-- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/armsrc/felica.c b/armsrc/felica.c index 084ca6eee..b7f8b01c0 100644 --- a/armsrc/felica.c +++ b/armsrc/felica.c @@ -241,8 +241,8 @@ static uint8_t felica_select_card(felica_card_select_t *card) { // We try 10 times, or if answer was received. int len = 25; do { - // end-of-reception response packet data, wait approx. 501μs - // end-of-transmission command packet data, wait approx. 197μs + // end-of-reception response packet data, wait approx. 501µs + // end-of-transmission command packet data, wait approx. 197µs // polling card TransmitFor18092_AsReader(poll, sizeof(poll), NULL, 1, 0); diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c index 0b1216edf..d23dba6af 100644 --- a/armsrc/hitagu.c +++ b/armsrc/hitagu.c @@ -58,24 +58,24 @@ static void update_tag_max_page_by_icr(void) { switch (tag.icr) { case HITAGU_ICR_STANDARD: tag.max_page = HITAGU_MAX_PAGE_STANDARD; - DBG Dbprintf("Detected standard Hitag μ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + DBG Dbprintf("Detected standard Hitag µ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); break; case HITAGU_ICR_ADVANCED: tag.max_page = HITAGU_MAX_PAGE_ADVANCED; - DBG Dbprintf("Detected Hitag μ advanced (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + DBG Dbprintf("Detected Hitag µ advanced (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); break; case HITAGU_ICR_ADVANCED_PLUS: tag.max_page = HITAGU_MAX_PAGE_ADVANCED_PLUS; - DBG Dbprintf("Detected Hitag μ advanced+ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + DBG Dbprintf("Detected Hitag µ advanced+ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); break; case HITAGU_ICR_8265: tag.max_page = HITAGU_MAX_PAGE_8265; - DBG Dbprintf("Detected Hitag μ 8265 (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + DBG Dbprintf("Detected Hitag µ 8265 (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); break; default: // Unknown ICR, use standard size as fallback tag.max_page = HITAGU_MAX_PAGE_STANDARD; - DBG Dbprintf("Unknown Hitag μ ICR: 0x%02X, defaulting to max page: 0x%02X", tag.icr, tag.max_page); + DBG Dbprintf("Unknown Hitag µ ICR: 0x%02X, defaulting to max page: 0x%02X", tag.icr, tag.max_page); break; } } diff --git a/armsrc/lfops.c b/armsrc/lfops.c index 46c1e0d3f..bc9c28fd7 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -64,11 +64,11 @@ SAM7S has several timers, we will use the source TIMER_CLOCK1 (aka AT91C_TC_CLKS TIMER_CLOCK1 = MCK/2, MCK is running at 48 MHz, Timer is running at 48/2 = 24 MHz New timer implementation in ticks.c, which is used in LFOPS.c - 1 μs = 1.5 ticks - 1 fc = 8 μs = 12 ticks + 1 µs = 1.5 ticks + 1 fc = 8 µs = 12 ticks Terms you find in different datasheets and how they match. -1 Cycle = 8 microseconds (μs) == 1 field clock (fc) +1 Cycle = 8 microseconds (µs) == 1 field clock (fc) Note about HITAG timing Hitag units (T0) have duration of 8 microseconds (us), which is 1/125000 per second (carrier) @@ -80,7 +80,7 @@ Hitag units (T0) have duration of 8 microseconds (us), which is 1/125000 per sec ========================================================================================================== ATA5577 Downlink Protocol Timings. - Note: All absolute times assume TC = 1 / fC = 8 μs (fC = 125 kHz) + Note: All absolute times assume TC = 1 / fC = 8 µs (fC = 125 kHz) Note: These timings are from the datasheet and doesn't map the best to the features of the RVD4 LF antenna. RDV4 LF antenna has high voltage and the drop of power when turning off the rf field takes about 1-2 TC longer. diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c index 0915037fa..da8594016 100644 --- a/client/src/cmdlfhitagu.c +++ b/client/src/cmdlfhitagu.c @@ -361,16 +361,16 @@ static int CmdLFHitagURead(const char *Cmd) { if (icr == HITAGU_ICR_STANDARD) { user_blocks = HITAGU_MAX_PAGE_STANDARD; - PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_ADVANCED) { user_blocks = HITAGU_MAX_PAGE_ADVANCED; - PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; - PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_8265) { user_blocks = HITAGU_MAX_PAGE_8265; - PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else { user_blocks = HITAGU_MAX_PAGE_STANDARD; PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); @@ -509,16 +509,16 @@ static int CmdLFHitagUDump(const char *Cmd) { if (icr == HITAGU_ICR_STANDARD) { user_blocks = HITAGU_MAX_PAGE_STANDARD; - PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_ADVANCED) { user_blocks = HITAGU_MAX_PAGE_ADVANCED; - PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; - PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else if (icr == HITAGU_ICR_8265) { user_blocks = HITAGU_MAX_PAGE_8265; - PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + PrintAndLogEx(INFO, "Hitag µ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); } else { user_blocks = HITAGU_MAX_PAGE_STANDARD; PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index 9c477a69a..a1d4e7350 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -155,7 +155,7 @@ This is the cheapest and most common ID82xx chip available. It is usually sold a #### Characteristics -* Chip is likely a cut down version of Hitag μ (micro) clone +* Chip is likely a cut down version of Hitag µ (micro) clone * UID `00 00 00 00 00 00` * Password protection (4b), usually "00000000"(default) or "9AC4999C"(FURUI) * Config block 0xFF diff --git a/include/hitag.h b/include/hitag.h index a8e64d452..efc854d90 100644 --- a/include/hitag.h +++ b/include/hitag.h @@ -47,16 +47,16 @@ #define HITAGU_CONFIG_PADR 0xFF #define HITAGU_PASSWORD_PADR 0xFE -// Hitag μ IC Revision (ICR) values -#define HITAGU_ICR_STANDARD 0x10 // Standard Hitag μ -#define HITAGU_ICR_ADVANCED 0x20 // Hitag μ advanced -#define HITAGU_ICR_ADVANCED_PLUS 0x30 // Hitag μ advanced+ +// Hitag µ IC Revision (ICR) values +#define HITAGU_ICR_STANDARD 0x10 // Standard Hitag µ +#define HITAGU_ICR_ADVANCED 0x20 // Hitag µ advanced +#define HITAGU_ICR_ADVANCED_PLUS 0x30 // Hitag µ advanced+ #define HITAGU_ICR_8265 0x80 // 8265 -// Hitag μ memory sizes based on ICR -#define HITAGU_MAX_PAGE_STANDARD 0x04 // 4 blocks (0x00-0x03) for standard Hitag μ -#define HITAGU_MAX_PAGE_ADVANCED 0x10 // 16 blocks (0x00-0x0F) for Hitag μ advanced -#define HITAGU_MAX_PAGE_ADVANCED_PLUS 0x37 // 56 blocks (0x00-0x36) for Hitag μ advanced+ +// Hitag µ memory sizes based on ICR +#define HITAGU_MAX_PAGE_STANDARD 0x04 // 4 blocks (0x00-0x03) for standard Hitag µ +#define HITAGU_MAX_PAGE_ADVANCED 0x10 // 16 blocks (0x00-0x0F) for Hitag µ advanced +#define HITAGU_MAX_PAGE_ADVANCED_PLUS 0x37 // 56 blocks (0x00-0x36) for Hitag µ advanced+ #define HITAGU_MAX_PAGE_8265 0x0F // 15 blocks (0x00-0x0E) for 8265 // need to see which limits these cards has diff --git a/include/protocols.h b/include/protocols.h index 70795fcd0..43cb94c29 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -950,7 +950,7 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define HITAGS_WRITE_BLOCK 0x90 // 1001 WRITE BLOCK #define HITAGS_QUIET 0x70 // 0111 QUIET -// Hitag μ flags +// Hitag µ flags #define HITAGU_FLAG_PEXT 0x01 // 0b00001 - Protocol EXTension flag #define HITAGU_FLAG_INV 0x02 // 0b00010 - INVentory flag #define HITAGU_FLAG_CRCT 0x04 // 0b00100 - CRC Transponder flag @@ -959,7 +959,7 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define HITAGU_FLAG_RFU 0x08 // 0b01000 - Reserved For Use flag (when INV=1, always 0) #define HITAGU_FLAG_NOS 0x10 // 0b10000 - Number Of Slots flag (when INV=1) -// Hitag μ commands (6-bit) +// Hitag µ commands (6-bit) #define HITAGU_CMD_LOGIN 0x28 // 0b101000 - Login command #define HITAGU_CMD_INVENTORY 0x00 // 0b000000 - Inventory command #define HITAGU_CMD_READ_MULTIPLE_BLOCK 0x12 // 0b010010 - Read multiple block command From 87c2e82e2f2c0cbb3570fe08e3e374ea7dd36523 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 12:53:24 +0100 Subject: [PATCH 32/43] style --- armsrc/hitagu.c | 2 +- client/src/cmdlfem410x.c | 2 +- client/src/pm3line_vocabulary.h | 7 ++ doc/commands.json | 119 ++++++++++++++++++++++++++++++-- doc/commands.md | 15 ++++ include/pm3_cmd.h | 2 +- 6 files changed, 138 insertions(+), 9 deletions(-) diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c index d23dba6af..77549aa2d 100644 --- a/armsrc/hitagu.c +++ b/armsrc/hitagu.c @@ -672,7 +672,7 @@ int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer) { DBG Dbprintf("HitagU UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); if (uid) { - *uid = BSWAP_48(*(uint64_t*)&tag.uid); + *uid = BSWAP_48(*(uint64_t *)&tag.uid); } exit: diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index 3d4d5f3f2..daaad2acc 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -798,7 +798,7 @@ static int CmdEM410xClone(const char *Cmd) { config.datarate = 0x02; break; } - packet.data[0] = reflect8(*(uint8_t*)&config); + packet.data[0] = reflect8(*(uint8_t *)&config); packet.page = HITAGU_CONFIG_PADR; // Config block break; } diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index b51758eae..bfe823459 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -691,6 +691,13 @@ const static vocabulary_t vocabulary[] = { { 0, "lf hitag hts restore" }, { 0, "lf hitag hts wrbl" }, { 0, "lf hitag hts sim" }, + { 1, "lf hitag htu help" }, + { 1, "lf hitag htu list" }, + { 0, "lf hitag htu reader" }, + { 0, "lf hitag htu rdbl" }, + { 0, "lf hitag htu dump" }, + { 0, "lf hitag htu wrbl" }, + { 0, "lf hitag htu sim" }, { 1, "lf idteck help" }, { 1, "lf idteck demod" }, { 0, "lf idteck reader" }, diff --git a/doc/commands.json b/doc/commands.json index a5fe357a1..fbbd2501d 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -8841,12 +8841,13 @@ }, "lf em 410x clone": { "command": "lf em 410x clone", - "description": "clone a EM410x ID to a T55x7, Q5/T5555, EM4305/4469 or Hitag S/8211/8268/8310 tag.", + "description": "clone a EM410x ID to a T55x7, Q5/T5555, EM4305/4469, Hitag S/8211/8268/8310 or Hitag \u00b5/8265 tag.", "notes": [ "lf em 410x clone --id 0F0368568B -> encode for T55x7 tag", "lf em 410x clone --id 0F0368568B --q5 -> encode for Q5/T5555 tag", "lf em 410x clone --id 0F0368568B --em -> encode for EM4305/4469", - "lf em 410x clone --id 0F0368568B --hts -> encode for Hitag S/8211/8268/8310" + "lf em 410x clone --id 0F0368568B --hts -> encode for Hitag S/8211/8268/8310", + "lf em 410x clone --id 0F0368568B --htu -> encode for Hitag \u00b5/8265 tag" ], "offline": false, "options": [ @@ -8856,9 +8857,10 @@ "--q5 optional - specify writing to Q5/T5555 tag", "--em optional - specify writing to EM4305/4469 tag", "--hts optional - specify writing to Hitag S/8211/8268/8310 tag", + "--htu optional - specify writing to Hitag \u00b5/8265 tag", "--electra optional - add Electra blocks to tag" ], - "usage": "lf em 410x clone [-h] [--clk ] --id [--q5] [--em] [--hts] [--electra]" + "usage": "lf em 410x clone [-h] [--clk ] --id [--q5] [--em] [--hts] [--htu] [--electra]" }, "lf em 410x reader": { "command": "lf em 410x reader", @@ -9975,7 +9977,7 @@ }, "lf hitag help": { "command": "lf hitag help", - "description": "help This help list List Hitag trace history hts { Hitag S/8211 operations } test Perform self tests view Display content from tag dump file lookup Uses authentication trace to check for key in dictionary file --------------------------------------------------------------------------------------- lf hitag list available offline: yes Alias of `trace list -t hitag2` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List Hitag trace history hts { Hitag S/8211 operations } htu { Hitag \u00b5/8265 operations } test Perform self tests view Display content from tag dump file lookup Uses authentication trace to check for key in dictionary file --------------------------------------------------------------------------------------- lf hitag list available offline: yes Alias of `trace list -t hitag2` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "lf hitag list --frame -> show frame delay times", "lf hitag list -1 -> use trace buffer" @@ -10137,6 +10139,110 @@ ], "usage": "lf hitag hts wrbl [-h8] [--nrar ] [--crypto] [-k ] [-m ] -p -d " }, + "lf hitag htu dump": { + "command": "lf hitag htu dump", + "description": "Read all Hitag \u00b5 memory and save to file 82xx password mode: - default password 00000000", + "notes": [ + "lf hitag htu dump --82xx -> use def pwd", + "lf hitag htu dump --82xx -k 9AC4999C -> pwd mode" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-8, --82xx 82xx mode", + "-k, --key pwd, 4 hex bytes", + "-f, --file specify file name", + "--ns no save to file" + ], + "usage": "lf hitag htu dump [-h8] [-k ] [-f ] [--ns]" + }, + "lf hitag htu help": { + "command": "lf hitag htu help", + "description": "help This help list List Hitag \u00b5 trace history --------------------------------------------------------------------------------------- lf hitag htu list available offline: yes Alias of `trace list -t hitagu` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "notes": [ + "lf hitag htu list --frame -> show frame delay times", + "lf hitag htu list -1 -> use trace buffer" + ], + "offline": true, + "options": [ + "-h, --help This help", + "-1, --buffer use data from trace buffer", + "--frame show frame delay times", + "-c mark CRC bytes", + "-r show relative times (gap and duration)", + "-u display times in microseconds instead of clock cycles", + "-x show hexdump to convert to pcap(ng)", + "or to import into Wireshark using encapsulation type \"ISO 14443\"", + "-f, --file filename of dictionary" + ], + "usage": "lf hitag htu list [-h1crux] [--frame] [-f ]" + }, + "lf hitag htu rdbl": { + "command": "lf hitag htu rdbl", + "description": "Read Hitag \u00b5 memory. 82xx password mode: - default password 00000000", + "notes": [ + "lf hitag htu rdbl -p 1 -> Hitag \u00b5, plain mode", + "lf hitag htu rdbl -p 1 --82xx -> 82xx, password mode, def pass", + "lf hitag htu rdbl -p 1 --82xx -k 9AC4999C -> 82xx, password mode" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-8, --82xx 82xx mode", + "-k, --key pwd, 4 hex bytes", + "-p, --page block address to read from (def: 0)", + "-c, --count how many blocks to read. '0' reads all blocks (def: 1)" + ], + "usage": "lf hitag htu rdbl [-h8] [-k ] [-p ] [-c ]" + }, + "lf hitag htu reader": { + "command": "lf hitag htu reader", + "description": "Act as a Hitag \u00b5 reader. Look for Hitag \u00b5 tags until Enter or the pm3 button is pressed", + "notes": [ + "lf hitag htu reader", + "lf hitag htu reader -@ -> Continuous mode" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-@ continuous reader mode" + ], + "usage": "lf hitag htu reader [-h@]" + }, + "lf hitag htu sim": { + "command": "lf hitag htu sim", + "description": "Simulate Hitag \u00b5 transponder You need to `lf hitag htu eload` first", + "notes": [ + "lf hitag htu sim", + "lf hitag htu sim --82xx", + "lf hitag htu sim -t 30 -> set threshold to 30" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-8, --82xx simulate 82xx", + "-t, --threshold set edge detect threshold (def: 127)" + ], + "usage": "lf hitag htu sim [-h8] [-t ]" + }, + "lf hitag htu wrbl": { + "command": "lf hitag htu wrbl", + "description": "Write a block in Hitag \u00b5 memory. 82xx password mode: - default password 00000000", + "notes": [ + "lf hitag htu wrbl -p 6 -d 01020304 -> Hitag \u00b5, plain mode", + "lf hitag htu wrbl -p 6 -d 01020304 --82xx -> use def pwd", + "lf hitag htu wrbl -p 6 -d 01020304 --82xx -k 9AC4999C -> 82xx, password mode" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-8, --82xx 82xx mode", + "-k, --key pwd, 4 hex bytes", + "-p, --page block address to write to", + "-d, --data data, 4 hex bytes" + ], + "usage": "lf hitag htu wrbl [-h8] [-k ] -p -d " + }, "lf hitag info": { "command": "lf hitag info", "description": "Hitag 2 tag information", @@ -13033,6 +13139,7 @@ "trace list -t hitag1 -> interpret as Hitag 1", "trace list -t hitag2 -> interpret as Hitag 2", "trace list -t hitags -> interpret as Hitag S", + "trace list -t hitagu -> interpret as Hitag \u00b5", "trace list -t iclass -> interpret as iCLASS", "trace list -t legic -> interpret as LEGIC", "trace list -t lto -> interpret as LTO-CM", @@ -13248,8 +13355,8 @@ } }, "metadata": { - "commands_extracted": 761, + "commands_extracted": 767, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-19T10:24:44" + "extracted_on": "2025-03-19T11:50:24" } } diff --git a/doc/commands.md b/doc/commands.md index e9f37737a..12312b7fc 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -1097,6 +1097,21 @@ Check column "offline" for their availability. |`lf hitag hts sim `|N |`Simulate Hitag S transponder` +### lf hitag htu + + { Hitag µ/8265 operations } + +|command |offline |description +|------- |------- |----------- +|`lf hitag htu help `|Y |`This help` +|`lf hitag htu list `|Y |`List Hitag µ trace history` +|`lf hitag htu reader `|N |`Act like a Hitag µ reader` +|`lf hitag htu rdbl `|N |`Read Hitag µ block` +|`lf hitag htu dump `|N |`Dump Hitag µ blocks to a file` +|`lf hitag htu wrbl `|N |`Write Hitag µ block` +|`lf hitag htu sim `|N |`Simulate Hitag µ transponder` + + ### lf idteck { Idteck RFIDs... } diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index d23f00870..89600e8e4 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -282,7 +282,7 @@ typedef struct { typedef struct { // 64KB SRAM -> 524288 bits(max sample num) < 2^30 - uint32_t samples : +uint32_t samples : LF_SAMPLES_BITS; bool realtime : 1; bool verbose : 1; From c3390a2e0e65288959c91812081c6b02236ccb72 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 13:37:53 +0100 Subject: [PATCH 33/43] added lf t55 Be-Tech identification --- CHANGELOG.md | 1 + client/src/cmdlft55xx.c | 6 +++++- client/src/cmdlft55xx.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c7021e2..a351cf1c1 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 Be-Tech identification (@iceman1001) - Added `lf em 410x clone --htu` clone EM410x ID to Hitag µ/8265 (@douniwan5788) - Added `lf hitag htu` support for Hitag µ/8265 (@douniwan5788) - Added `hf mfu aesauth` based on existing UL AES support (@doegox) diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index 33f3cdf7c..c38f96bcf 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -1551,6 +1551,7 @@ bool testKnownConfigBlock(uint32_t block0) { case T55X7_JABLOTRON_CONFIG_BLOCK: case T55X7_PYRONIX_CONFIG_BLOCK: case T55X7_TEXECOM_CONFIG_BLOCK: + case T55X7_BETECH_CONFIG_BLOCK: return true; } return false; @@ -2302,12 +2303,15 @@ static void printT5x7KnownBlock0(uint32_t b0) { case T55X7_TEXECOM_CONFIG_BLOCK: snprintf(s + strlen(s), sizeof(s) - strlen(s), "Texecom "); break; + case T55X7_BETECH_CONFIG_BLOCK: + snprintf(s + strlen(s), sizeof(s) - strlen(s), "Be-Tech "); + break; default: break; } if (strlen(s) > 0) { - PrintAndLogEx(SUCCESS, "Config block match : " _YELLOW_("%s"), s); + PrintAndLogEx(SUCCESS, "Config block match... " _YELLOW_("%s"), s); } } diff --git a/client/src/cmdlft55xx.h b/client/src/cmdlft55xx.h index 9160050a5..70f861bae 100644 --- a/client/src/cmdlft55xx.h +++ b/client/src/cmdlft55xx.h @@ -44,6 +44,7 @@ #define T55X7_UNK_CONFIG_BLOCK 0x000880FA // ASK, Manchester, data rate 32, 7 data blocks STT, Inverse ... #define T55X7_PYRONIX_CONFIG_BLOCK 0x00088C40 // ASK, Manchester, data rate 32, 2 data blocks #define T55X7_TEXECOM_CONFIG_BLOCK 0x001C8020 // ASK, Manchester, data rate 128, 1 data block +#define T55X7_BETECH_CONFIG_BLOCK 0x001480E0 // ASK, Manchester, data rate 64, 7 data block // FDXB requires data inversion and BiPhase 57 is simply BiPhase 50 inverted, so we can either do it using the modulation scheme or the inversion flag // we've done both below to prove that it works either way, and the modulation value for BiPhase 50 in the Atmel data sheet of binary "10001" (17) is a typo, From c8cde55a5e3c4d27cb4385c824e4ddca12e7205a Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 15:22:55 +0100 Subject: [PATCH 34/43] fix OTP simulation --- armsrc/iso14443a.c | 30 +++++++----- client/src/cmdhf14a.c | 8 ++-- client/src/cmdhficlass.c | 3 +- client/src/cmdhfmf.c | 4 +- client/src/cmdlft55xx.c | 2 +- client/src/mifare/aiddesfire.c | 88 +++++++++++++++++----------------- 6 files changed, 72 insertions(+), 63 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index bdf59f08e..250b25dce 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1726,11 +1726,21 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin } p_response = NULL; } else if (receivedCmd[0] == MIFARE_ULC_WRITE && len == 8 && (tagType == 2 || tagType == 7)) { // Received a WRITE + + p_response = NULL; + // cmd + block + 4 bytes data + 2 bytes crc if (CheckCrc14A(receivedCmd, len)) { uint8_t block = receivedCmd[1]; + // sanity checks + if (block > pages) { + // send NACK 0x0, invalid argument + EmSend4bit(CARD_NACK_IV); + goto jump; + } + // OTP sanity check // Quite a bad one, one should look at all individual bits and see if anyone tries be set as zero // we cheat and do fat 00000000 check instead @@ -1739,23 +1749,20 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin // OTP can't be set back to zero // send NACK 0x0 == invalid argument, EmSend4bit(CARD_NACK_IV); + goto jump; } } - if (block > pages) { - // send NACK 0x0 == invalid argument - EmSend4bit(CARD_NACK_IV); - } else { - // first blocks of emu are header - emlSetMem_xt(&receivedCmd[2], block + (MFU_DUMP_PREFIX_LENGTH / 4), 1, 4); - // send ACK - EmSend4bit(CARD_ACK); - } + // first blocks of emu are header + emlSetMem_xt(&receivedCmd[2], block + (MFU_DUMP_PREFIX_LENGTH / 4), 1, 4); + // send ACK + EmSend4bit(CARD_ACK); + } else { // send NACK 0x1 == crc/parity error EmSend4bit(CARD_NACK_PA); } - p_response = NULL; + goto jump; } else if (receivedCmd[0] == MIFARE_ULC_COMP_WRITE && len == 4 && (tagType == 2 || tagType == 7)) { // cmd + block + 2 bytes crc if (CheckCrc14A(receivedCmd, len)) { @@ -2002,6 +2009,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin // Count number of other messages after a halt // if (order != ORDER_WUPA && lastorder == ORDER_HALTED) { happened2++; } +jump: cmdsRecvd++; @@ -3239,7 +3247,7 @@ void ReaderIso14443a(PacketCommandNG *c) { } if ((param & ISO14A_APDU) == ISO14A_APDU) { - uint8_t res; + uint8_t res = 0; arg0 = iso14_apdu( cmd, len, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 7bd292489..1a694bc49 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1206,9 +1206,9 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen } } - uint16_t cmdc = 0; + uint16_t cmdc = (ISO14A_APDU | ISO14A_NO_DISCONNECT); if (chainingin) { - cmdc = ISO14A_SEND_CHAINING; + cmdc |= ISO14A_SEND_CHAINING; } // "Command APDU" length should be 5+255+1, but javacard's APDU buffer might be smaller - 133 bytes @@ -1216,9 +1216,9 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen // here length PM3_CMD_DATA_SIZE=512 // timeout must be authomatically set by "get ATS" if (datain) { - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_APDU | ISO14A_NO_DISCONNECT | cmdc, (datainlen & 0x1FF), 0, datain, datainlen & 0x1FF); + SendCommandMIX(CMD_HF_ISO14443A_READER, cmdc, (datainlen & 0x1FF), 0, datain, (datainlen & 0x1FF)); } else { - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_APDU | ISO14A_NO_DISCONNECT | cmdc, 0, 0, NULL, 0); + SendCommandMIX(CMD_HF_ISO14443A_READER, cmdc, 0, 0, NULL, 0); } PacketResponseNG resp; diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index aa73c7e1c..885c27c1d 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1482,7 +1482,8 @@ static void iclass_decode_credentials(uint8_t *data) { char *pbin = binstr; while (strlen(pbin) && *(++pbin) == '0'); - PrintAndLogEx(SUCCESS, "Binary... %zu - " _GREEN_("%s"), strlen(pbin), pbin); + PrintAndLogEx(SUCCESS, "Binary... " _GREEN_("%s") " ( %zu )", pbin, strlen(pbin)); + PrintAndLogEx(NORMAL, ""); decode_wiegand(top, mid, bot, 0); } diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index fb3d80218..be3eb80ec 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -6279,7 +6279,8 @@ static int CmdHF14AMfMAD(const char *Cmd) { if (sector > -1) { // decode it - PrintAndLogEx(INFO, ""); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "------------------------- " _CYAN_("Wiegand") " ---------------------------"); PrintAndLogEx(INFO, _CYAN_("HID PACS detected")); uint8_t pacs_sector[MFBLOCK_SIZE * 3] = {0}; @@ -6302,7 +6303,6 @@ static int CmdHF14AMfMAD(const char *Cmd) { PrintAndLogEx(SUCCESS, "Binary... " _GREEN_("%s"), pbin); - PrintAndLogEx(INFO, "Wiegand decode"); decode_wiegand(top, mid, bot, 0); } } diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index c38f96bcf..f59679972 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -2303,7 +2303,7 @@ static void printT5x7KnownBlock0(uint32_t b0) { case T55X7_TEXECOM_CONFIG_BLOCK: snprintf(s + strlen(s), sizeof(s) - strlen(s), "Texecom "); break; - case T55X7_BETECH_CONFIG_BLOCK: + case T55X7_BETECH_CONFIG_BLOCK: snprintf(s + strlen(s), sizeof(s) - strlen(s), "Be-Tech "); break; default: diff --git a/client/src/mifare/aiddesfire.c b/client/src/mifare/aiddesfire.c index f1d7609f7..26bf9adf6 100644 --- a/client/src/mifare/aiddesfire.c +++ b/client/src/mifare/aiddesfire.c @@ -94,7 +94,7 @@ typedef enum { const char *nxp_cluster_to_text(uint8_t cluster) { switch (cluster) { case CL_ADMIN: - return "card administration"; + return "Card administration"; case CL_MISC1: case CL_MISC2: case CL_MISC3: @@ -102,40 +102,40 @@ const char *nxp_cluster_to_text(uint8_t cluster) { case CL_MISC5: case CL_MISC6: case CL_MISC7: - return "miscellaneous applications"; + return "Miscellaneous applications"; case CL_AIRLINES: - return "airlines"; + return "Airlines"; case CL_FERRY: - return "ferry traffic"; + return "Ferry traffic"; case CL_RAIL: - return "railway services"; + return "Railway services"; case CL_MISC: - return "miscellaneous applications"; + return "Miscellaneous applications"; case CL_TRANSPORT: - return "transport"; + return "Transport"; case CL_SECURITY: - return "security solutions"; + return "Security solutions"; case CL_CITYTRAFFIC: - return "city traffic"; + return "City traffic"; case CL_CZECH_RAIL: return "Czech Railways"; case CL_BUS: - return "bus services"; + return "Bus services"; case CL_MMT: - return "multi modal transit"; + return "Multi modal transit"; case CL_TAXI: - return "taxi"; + return "Taxi"; case CL_TOLL: - return "road toll"; + return "Road toll"; case CL_GENERIC_TRANS: - return "generic transport"; + return "Generic transport"; case CL_COMPANY_SERVICES: - return "company services"; + return "Company services"; case CL_CITYCARD: - return "city card services"; + return "City card services"; case CL_ACCESS_CONTROL_1: case CL_ACCESS_CONTROL_2: - return "access control & security"; + return "Access control & security"; case CL_VIGIK: return "VIGIK"; case CL_NED_DEFENCE: @@ -145,63 +145,63 @@ const char *nxp_cluster_to_text(uint8_t cluster) { case CL_EU: return "European Union Institutions"; case CL_SKI_TICKET: - return "ski ticketing"; + return "Ski ticketing"; case CL_SOAA: return "SOAA standard for offline access standard"; case CL_ACCESS2: - return "access control & security"; + return "Access control & security"; case CL_FOOD: - return "food"; + return "Food"; case CL_NONFOOD: - return "non-food trade"; + return "Non-food trade"; case CL_HOTEL: - return "hotel"; + return "Hotel"; case CL_LOYALTY: - return "loyalty"; + return "Loyalty"; case CL_AIRPORT: - return "airport services"; + return "Airport services"; case CL_CAR_RENTAL: - return "car rental"; + return "Car rental"; case CL_NED_GOV: return "Dutch government"; case CL_ADMIN2: - return "administration services"; + return "Administration services"; case CL_PURSE: - return "electronic purse"; + return "Electronic purse"; case CL_TV: - return "television"; + return "Television"; case CL_CRUISESHIP: - return "cruise ship"; + return "Cruise ship"; case CL_IOPTA: return "IOPTA"; case CL_METERING: - return "metering"; + return "Metering"; case CL_TELEPHONE: - return "telephone"; + return "Telephone"; case CL_HEALTH: - return "health services"; + return "Health services"; case CL_WAREHOUSE: - return "warehouse"; + return "Warehouse"; case CL_BANKING: - return "banking"; + return "Banking"; case CL_ENTERTAIN: - return "entertainment & sports"; + return "Entertainment & sports"; case CL_PARKING: - return "car parking"; + return "Car parking"; case CL_FLEET: - return "fleet management"; + return "Fleet management"; case CL_FUEL: - return "fuel, gasoline"; + return "Fuel, gasoline"; case CL_INFO: - return "info services"; + return "Info services"; case CL_PRESS: - return "press"; + return "Press"; case CL_NFC: return "NFC Forum"; case CL_COMPUTER: - return "computer"; + return "Computer"; case CL_MAIL: - return "mail"; + return "Mail"; case CL_AMISC: case CL_AMISC1: case CL_AMISC2: @@ -210,11 +210,11 @@ const char *nxp_cluster_to_text(uint8_t cluster) { case CL_AMISC5: case CL_AMISC6: case CL_AMISC7: - return "miscellaneous applications"; + return "Miscellaneous applications"; default: break; } - return "reserved"; + return "Reserved"; } static json_t *df_known_aids = NULL; From 440d283a536eee6662f1e70742653f04fba8d5aa Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 15:24:39 +0100 Subject: [PATCH 35/43] in the philosofy to have short and direct params , the trace list -t hitag types has been shortend from hitag1 - ht1 --- CHANGELOG.md | 1 + client/src/cmdtrace.c | 16 ++++++++-------- doc/commands.json | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a351cf1c1..f666f4ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Changed `trace list -t` - shortend the hitag types (@iceman1001) - Added Be-Tech identification (@iceman1001) - Added `lf em 410x clone --htu` clone EM410x ID to Hitag µ/8265 (@douniwan5788) - Added `lf hitag htu` support for Hitag µ/8265 (@douniwan5788) diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index d563d4478..2cc28446b 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -1320,10 +1320,10 @@ int CmdTraceList(const char *Cmd) { "trace list -t cryptorf -> interpret as " _YELLOW_("CryptoRF") "\n\n" "trace list -t des -> interpret as " _YELLOW_("MIFARE DESFire") "\n" "trace list -t felica -> interpret as " _YELLOW_("ISO18092 / FeliCa") "\n" - "trace list -t hitag1 -> interpret as " _YELLOW_("Hitag 1") "\n" - "trace list -t hitag2 -> interpret as " _YELLOW_("Hitag 2") "\n" - "trace list -t hitags -> interpret as " _YELLOW_("Hitag S") "\n" - "trace list -t hitagu -> interpret as " _YELLOW_("Hitag µ") "\n" + "trace list -t ht1 -> interpret as " _YELLOW_("Hitag 1") "\n" + "trace list -t ht2 -> interpret as " _YELLOW_("Hitag 2") "\n" + "trace list -t hts -> interpret as " _YELLOW_("Hitag S") "\n" + "trace list -t htu -> interpret as " _YELLOW_("Hitag µ") "\n" "trace list -t iclass -> interpret as " _YELLOW_("iCLASS") "\n" "trace list -t legic -> interpret as " _YELLOW_("LEGIC") "\n" "trace list -t lto -> interpret as " _YELLOW_("LTO-CM") "\n" @@ -1388,10 +1388,10 @@ int CmdTraceList(const char *Cmd) { else if (strcmp(type, "cryptorf") == 0) protocol = PROTO_CRYPTORF; else if (strcmp(type, "des") == 0) protocol = MFDES; else if (strcmp(type, "felica") == 0) protocol = FELICA; - else if (strcmp(type, "hitag1") == 0) protocol = PROTO_HITAG1; - else if (strcmp(type, "hitag2") == 0) protocol = PROTO_HITAG2; - else if (strcmp(type, "hitags") == 0) protocol = PROTO_HITAGS; - else if (strcmp(type, "hitagu") == 0) protocol = PROTO_HITAGU; + else if (strcmp(type, "ht1") == 0) protocol = PROTO_HITAG1; + else if (strcmp(type, "ht2") == 0) protocol = PROTO_HITAG2; + else if (strcmp(type, "hts") == 0) protocol = PROTO_HITAGS; + else if (strcmp(type, "htu") == 0) protocol = PROTO_HITAGU; else if (strcmp(type, "iclass") == 0) protocol = ICLASS; else if (strcmp(type, "legic") == 0) protocol = LEGIC; else if (strcmp(type, "lto") == 0) protocol = LTO; diff --git a/doc/commands.json b/doc/commands.json index fbbd2501d..31527f680 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13136,10 +13136,10 @@ "", "trace list -t des -> interpret as MIFARE DESFire", "trace list -t felica -> interpret as ISO18092 / FeliCa", - "trace list -t hitag1 -> interpret as Hitag 1", - "trace list -t hitag2 -> interpret as Hitag 2", - "trace list -t hitags -> interpret as Hitag S", - "trace list -t hitagu -> interpret as Hitag \u00b5", + "trace list -t ht1 -> interpret as Hitag 1", + "trace list -t ht2 -> interpret as Hitag 2", + "trace list -t hts -> interpret as Hitag S", + "trace list -t htu -> interpret as Hitag \u00b5", "trace list -t iclass -> interpret as iCLASS", "trace list -t legic -> interpret as LEGIC", "trace list -t lto -> interpret as LTO-CM", @@ -13357,6 +13357,6 @@ "metadata": { "commands_extracted": 767, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-19T11:50:24" + "extracted_on": "2025-03-19T14:19:20" } } From 1bd811f7a139d1f6f012c50e9880f1c56b6acd7e Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 15:34:20 +0100 Subject: [PATCH 36/43] better OTP update sanity check by @jmichelp. Checks every bytewise if any bit is 0 --- armsrc/iso14443a.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 250b25dce..e95c36662 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1742,12 +1742,17 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin } // OTP sanity check - // Quite a bad one, one should look at all individual bits and see if anyone tries be set as zero - // we cheat and do fat 00000000 check instead if (block == 0x03) { - if (memcmp(receivedCmd + 2, "\x00\x00\x00\x00", 4) == 0) { - // OTP can't be set back to zero - // send NACK 0x0 == invalid argument, + + uint8_t orig[4] = {0}; + emlGet(orig, 12 + MFU_DUMP_PREFIX_LENGTH, 4); + + bool risky = false; + for (int i = 0; i < len; i++) { + risky |= orig[i] & ~receivedCmd[2 + i]; + } + + if (risky) { EmSend4bit(CARD_NACK_IV); goto jump; } From 76fa254c0f8e1d4c7b01243ea5cc795701815825 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 15:35:40 +0100 Subject: [PATCH 37/43] only check 4 bytes --- armsrc/iso14443a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index e95c36662..ed2ce16e7 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1748,7 +1748,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin emlGet(orig, 12 + MFU_DUMP_PREFIX_LENGTH, 4); bool risky = false; - for (int i = 0; i < len; i++) { + for (int i = 0; i < 4; i++) { risky |= orig[i] & ~receivedCmd[2 + i]; } From 823069f5005d03c1e120d960c0d7cf61c7c0be99 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 20:46:29 +0100 Subject: [PATCH 38/43] fixed to shortend type --- client/src/cmdlfhitagu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c index da8594016..f56c9560d 100644 --- a/client/src/cmdlfhitagu.c +++ b/client/src/cmdlfhitagu.c @@ -660,7 +660,7 @@ static int CmdLFHitagUSim(const char *Cmd) { return PM3_SUCCESS; } -static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "hitagu"); } +static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "htu"); } static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help" }, From 0107e212b002f2881b9355abb0b768380fa8f7c6 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 20:50:43 +0100 Subject: [PATCH 39/43] fix list for shortend type --- client/src/cmdlfhitag.c | 2 +- client/src/cmdlfhitaghts.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 81e38edc4..8e49802aa 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -80,7 +80,7 @@ static size_t nbytes(size_t nbits) { */ static int CmdLFHitagList(const char *Cmd) { - return CmdTraceListAlias(Cmd, "lf hitag", "hitag2"); + return CmdTraceListAlias(Cmd, "lf hitag", "ht2"); /* uint8_t *got = calloc(PM3_CMD_DATA_SIZE, sizeof(uint8_t)); if (!got) { diff --git a/client/src/cmdlfhitaghts.c b/client/src/cmdlfhitaghts.c index 13846efc9..af4fe9978 100644 --- a/client/src/cmdlfhitaghts.c +++ b/client/src/cmdlfhitaghts.c @@ -995,7 +995,7 @@ static int CmdLFHitagSSim(const char *Cmd) { } static int CmdLFHitagSList(const char *Cmd) { - return CmdTraceListAlias(Cmd, "lf hitag hts", "hitags"); + return CmdTraceListAlias(Cmd, "lf hitag hts", "hts"); } static command_t CommandTable[] = { From 9965c784bf13a788cff7b1a9a4ef0d32c1a46887 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 19 Mar 2025 21:11:05 +0100 Subject: [PATCH 40/43] style fixes --- client/src/cmdlfhitagu.c | 190 +++++++++++++++++++++++++-------------- doc/commands.json | 8 +- 2 files changed, 129 insertions(+), 69 deletions(-) diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c index f56c9560d..3758415ed 100644 --- a/client/src/cmdlfhitagu.c +++ b/client/src/cmdlfhitagu.c @@ -55,91 +55,118 @@ void annotateHitagU(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, size_t exp_len = snprintf(exp, size, "Flg:"); - if (flag & HITAGU_FLAG_PEXT) { + if ((flag & HITAGU_FLAG_PEXT) == HITAGU_FLAG_PEXT) { exp_len += snprintf(exp + exp_len, size - exp_len, " PEXT"); } - if (flag & HITAGU_FLAG_INV) { + + if ((flag & HITAGU_FLAG_INV) == HITAGU_FLAG_INV) { + exp_len += snprintf(exp + exp_len, size - exp_len, " INV"); - if (flag & HITAGU_FLAG_RFU) { + + if ((flag & HITAGU_FLAG_RFU) == HITAGU_FLAG_RFU) { exp_len += snprintf(exp + exp_len, size - exp_len, " RFU"); } - if (flag & HITAGU_FLAG_NOS) { + + if ((flag & HITAGU_FLAG_NOS) == HITAGU_FLAG_NOS) { exp_len += snprintf(exp + exp_len, size - exp_len, " NOS"); } + } else { - if (flag & HITAGU_FLAG_SEL) { + + if ((flag & HITAGU_FLAG_SEL) == HITAGU_FLAG_SEL) { exp_len += snprintf(exp + exp_len, size - exp_len, " SEL"); } - if (flag & HITAGU_FLAG_ADR) { + + if ((flag & HITAGU_FLAG_ADR) == HITAGU_FLAG_ADR) { exp_len += snprintf(exp + exp_len, size - exp_len, " ADR"); has_uid = true; } } - if (flag & HITAGU_FLAG_CRCT) { + + if ((flag & HITAGU_FLAG_CRCT) == HITAGU_FLAG_CRCT) { exp_len += snprintf(exp + exp_len, size - exp_len, " CRCT"); } exp_len += snprintf(exp + exp_len, size - exp_len, "|Cmd: "); + switch (command) { case HITAGU_CMD_LOGIN: { + bool has_mfc = false; - if (cmdsize == 6 + has_uid * HITAGU_UID_SIZE || cmdsize == 8 + has_uid * HITAGU_UID_SIZE) { + + if (cmdsize == (6 + (has_uid * HITAGU_UID_SIZE)) || cmdsize == (8 + (has_uid * HITAGU_UID_SIZE))) { + exp_len += snprintf(exp + exp_len, size - exp_len, "8265 LOGIN"); - } else if (cmdsize == 7 + has_uid * HITAGU_UID_SIZE || cmdsize == 9 + has_uid * HITAGU_UID_SIZE) { - uint8_t mfc; + + } else if (cmdsize == (7 + (has_uid * HITAGU_UID_SIZE)) || cmdsize == (9 + (has_uid * HITAGU_UID_SIZE))) { + + uint8_t mfc = 0; concatbits(&mfc, 0, cmd, 5 + 6 + 8 + 32, 8, false); exp_len += snprintf(exp + exp_len, size - exp_len, "LOGIN mfc:%02x ", mfc); has_mfc = true; } + if (has_uid) { - uint8_t uid[HITAGU_UID_SIZE]; + uint8_t uid[HITAGU_UID_SIZE] = {0}; concatbits(uid, 0, cmd, 5 + 6 + has_mfc * 8 + 32, HITAGU_UID_SIZE * 8, false); exp_len += snprintf(exp + exp_len, size - exp_len, " uid:%s", sprint_hex_inrow(uid, HITAGU_UID_SIZE)); } - uint8_t password[HITAG_PASSWORD_SIZE]; + + uint8_t password[HITAG_PASSWORD_SIZE] = {0}; concatbits(password, 0, cmd, 5 + 6 + has_mfc * 8 + has_uid * HITAGU_UID_SIZE * 8, HITAG_PASSWORD_SIZE * 8, false); exp_len += snprintf(exp + exp_len, size - exp_len, " pwd:%s", sprint_hex_inrow(password, HITAG_PASSWORD_SIZE)); break; } - case HITAGU_CMD_INVENTORY: + case HITAGU_CMD_INVENTORY: { exp_len += snprintf(exp + exp_len, size - exp_len, "INVENTORY"); break; + } case HITAGU_CMD_READ_MULTIPLE_BLOCK: { - uint8_t block_addr; + uint8_t block_addr = 0; concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); - uint8_t block_count; + uint8_t block_count = 0; concatbits(&block_count, 0, cmd, 5 + 6 + 8, 8, false); - exp_len += snprintf(exp + exp_len, size - exp_len, "READ MULTIPLE BLOCK start:%d num:%d", reflect8(block_addr), reflect8(block_count)); + exp_len += snprintf(exp + exp_len, size - exp_len, "READ MULTIPLE BLOCK start:%d num:%d" + , reflect8(block_addr) + , reflect8(block_count) + ); break; } case HITAGU_CMD_WRITE_SINGLE_BLOCK: { - uint8_t block_addr; + uint8_t block_addr = 0; concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); - uint8_t block_data[4]; + uint8_t block_data[4] = {0}; concatbits(block_data, 0, cmd, 5 + 6 + 8, 32, false); - exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE SINGLE BLOCK start:%d data:[%s]", - reflect8(block_addr), sprint_hex_inrow(block_data, 4)); + exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE SINGLE BLOCK start:%d data:[%s]" + , reflect8(block_addr) + , sprint_hex_inrow(block_data, 4) + ); break; } - case HITAGU_CMD_SELECT: + case HITAGU_CMD_SELECT: { exp_len += snprintf(exp + exp_len, size - exp_len, "SELECT"); break; - case HITAGU_CMD_SYSINFO: + } + case HITAGU_CMD_SYSINFO: { exp_len += snprintf(exp + exp_len, size - exp_len, "GET SYSTEM INFORMATION"); break; - case HITAGU_CMD_READ_UID: + } + case HITAGU_CMD_READ_UID: { exp_len += snprintf(exp + exp_len, size - exp_len, "READ UID"); break; - case HITAGU_CMD_STAY_QUIET: + } + case HITAGU_CMD_STAY_QUIET: { exp_len += snprintf(exp + exp_len, size - exp_len, "STAY QUIET"); break; - default: + } + default: { exp_len += snprintf(exp + exp_len, size - exp_len, "Unknown 0x%02X", command); break; + } } } } @@ -206,10 +233,12 @@ static int process_hitagu_common_args(CLIParserContext *ctx, lf_hitag_data_t *co packet->cmd = HTUF_82xx; memcpy(packet->pwd, key, sizeof(packet->pwd)); PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in 82xx mode"); + } else if (use_password) { packet->cmd = HTUF_PASSWORD; memcpy(packet->pwd, key, sizeof(packet->pwd)); PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in password mode"); + } else { packet->cmd = HTUF_PLAIN; memcpy(packet->pwd, key, sizeof(packet->pwd)); @@ -220,43 +249,58 @@ static int process_hitagu_common_args(CLIParserContext *ctx, lf_hitag_data_t *co } static void print_error(int8_t reason) { + + //todo: USE ENUM OR DEFINES switch (reason) { - case 0: + case 0: { PrintAndLogEx(FAILED, "No data"); break; - case -2: + } + case -2: { PrintAndLogEx(FAILED, "UID Request failed!"); break; - case -3: + } + case -3: { PrintAndLogEx(FAILED, "Select UID failed!"); break; - case -4: + } + case -4: { PrintAndLogEx(FAILED, "No write access on block. Not authorized?"); break; - case -5: + } + case -5: { PrintAndLogEx(FAILED, "Write failed! Wrong password?"); break; - case -6: + } + case -6: { PrintAndLogEx(FAILED, "Error, " _YELLOW_("AUT=1") " This tag is configured in Authentication Mode"); break; - case -7: + } + case -7: { PrintAndLogEx(FAILED, "Error, unknown function"); break; - case -8: + } + case -8: { PrintAndLogEx(FAILED, "Authenticate failed!"); break; - case -9: + } + case -9: { PrintAndLogEx(FAILED, "No write access on block"); break; - case -10: + } + case -10: { PrintAndLogEx(FAILED, "Write to block failed!"); break; - case -11: + } + case -11: { PrintAndLogEx(FAILED, "Read block failed!"); break; - default: + } + default: { // PM3_REASON_UNKNOWN PrintAndLogEx(FAILED, "Error - Hitag µ failed"); + break; + } } } @@ -312,19 +356,21 @@ static int CmdLFHitagURead(const char *Cmd) { lf_hitag_data_t packet; - if (process_hitagu_common_args(ctx, &packet) < 0) return PM3_EINVARG; + if (process_hitagu_common_args(ctx, &packet) < 0) { + return PM3_EINVARG; + } uint32_t page = arg_get_int_def(ctx, 3, 0); if (page >= HITAGU_MAX_BLOCKS) { - PrintAndLogEx(WARNING, "Block address Invalid. Maximum is 255."); + PrintAndLogEx(WARNING, "Block address out-of-range. Max is 255, got %u", page); return PM3_EINVARG; } uint32_t count = arg_get_int_def(ctx, 4, 1); if (count > HITAGU_MAX_BLOCKS) { - PrintAndLogEx(WARNING, "No more than %d blocks can be read at once.", HITAGU_MAX_BLOCKS); + PrintAndLogEx(WARNING, "No more than %d blocks can be read at once", HITAGU_MAX_BLOCKS); return PM3_EINVARG; } @@ -334,14 +380,14 @@ static int CmdLFHitagURead(const char *Cmd) { packet.page_count = count; // packet.mode = 1; // for debug - PrintAndLogEx(INFO, "Read Hitag µ memory block %d, count %d", page, count); + PrintAndLogEx(INFO, "Read Hitag µ memory block " _YELLOW_("%d") ", count " _YELLOW_("%d"), page, count); clearCommandBuffer(); SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); SendCommandNG(CMD_BREAK_LOOP, NULL, 0); return PM3_ETIMEOUT; } @@ -392,10 +438,12 @@ static int CmdLFHitagURead(const char *Cmd) { } for (int i = 0; i < count; ++i) { + int page_addr = page + i; if (page_addr >= HITAGU_MAX_BLOCKS) { break; } + if (card->pages_reason[i] > 0) { PrintAndLogEx(SUCCESS, "% 3u | %s | " NOLF, page_addr, sprint_hex_ascii(card->pages[i], HITAGU_BLOCK_SIZE)); @@ -407,26 +455,36 @@ static int CmdLFHitagURead(const char *Cmd) { PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); // Hitag µ } else if (page_addr < HITAGU_MAX_PAGE_STANDARD) { - if (card->config_page.s.pwdW0_127) + + if (card->config_page.s.pwdW0_127) { PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); - else + } else { PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } + } else if (HITAGU_MAX_PAGE_STANDARD <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED) { - if (card->config_page.s.pwdW128_511) + + if (card->config_page.s.pwdW128_511) { PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); - else + } else { PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } + } else if (HITAGU_MAX_PAGE_ADVANCED <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED_PLUS) { + if (card->config_page.s.pwdRW512_max) { PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); } else { - if (card->config_page.s.pwdW512_max) + + if (card->config_page.s.pwdW512_max) { PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); - else + } else { PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } } - } else + } else { PrintAndLogEx(NORMAL, _YELLOW_("UNK ") NOLF); + } PrintAndLogEx(NORMAL, " | " NOLF); @@ -435,8 +493,9 @@ static int CmdLFHitagURead(const char *Cmd) { PrintAndLogEx(NORMAL, "Password"); } else if (page_addr == HITAGU_CONFIG_PADR) { PrintAndLogEx(NORMAL, "Config"); - } else + } else { PrintAndLogEx(NORMAL, "Data"); + } } else { PrintAndLogEx(INFO, "% 3u | -- -- -- -- | .... | N/A | " NOLF, page_addr); print_error(card->pages_reason[i]); @@ -493,7 +552,7 @@ static int CmdLFHitagUDump(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 5000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -540,7 +599,7 @@ static int CmdLFHitagUDump(const char *Cmd) { if (fnlen < 1) { char *fptr = filename; - fptr += snprintf(filename, sizeof(filename), "lf-hitagu-"); + fptr += snprintf(filename, sizeof(filename), "lf-htu-"); FillFileNameByUID(fptr, card->uid, "-dump", HITAGU_UID_SIZE); } @@ -593,7 +652,7 @@ static int CmdLFHitagUWrite(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGU_WRITE, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -617,7 +676,9 @@ static int CmdLFHitagUReader(const char *Cmd) { "lf hitag htu reader\n" "lf hitag htu reader -@ -> Continuous mode"); - void *argtable[] = {arg_param_begin, arg_lit0("@", NULL, "continuous reader mode"), arg_param_end}; + void *argtable[] = { + arg_param_begin, arg_lit0("@", NULL, "continuous reader mode"), arg_param_end + }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool cm = arg_get_lit(ctx, 1); CLIParserFree(ctx); @@ -627,7 +688,6 @@ static int CmdLFHitagUReader(const char *Cmd) { } do { - // read UID read_htu_uid(); } while (cm && kbd_enter_pressed() == false); @@ -663,16 +723,16 @@ static int CmdLFHitagUSim(const char *Cmd) { static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "htu"); } static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help" }, - {"list", CmdLFHitagUList, AlwaysAvailable, "List Hitag µ trace history" }, - {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("General") " ------------------------" }, - {"reader", CmdLFHitagUReader, IfPm3Hitag, "Act like a Hitag µ reader" }, - {"rdbl", CmdLFHitagURead, IfPm3Hitag, "Read Hitag µ block" }, - {"dump", CmdLFHitagUDump, IfPm3Hitag, "Dump Hitag µ blocks to a file" }, - {"wrbl", CmdLFHitagUWrite, IfPm3Hitag, "Write Hitag µ block" }, - {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Simulation") " -----------------------"}, - {"sim", CmdLFHitagUSim, IfPm3Hitag, "Simulate Hitag µ transponder" }, - {NULL, NULL, 0, NULL } + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"list", CmdLFHitagUList, AlwaysAvailable, "List Hitag µ trace history"}, + {"-----------", CmdHelp, IfPm3Hitag, "----------- " _CYAN_("General") " -----------"}, + {"reader", CmdLFHitagUReader, IfPm3Hitag, "Act like a Hitag µ reader"}, + {"rdbl", CmdLFHitagURead, IfPm3Hitag, "Read Hitag µ block"}, + {"dump", CmdLFHitagUDump, IfPm3Hitag, "Dump Hitag µ blocks to a file"}, + {"wrbl", CmdLFHitagUWrite, IfPm3Hitag, "Write Hitag µ block"}, + {"-----------", CmdHelp, IfPm3Hitag, "----------- " _CYAN_("Simulation") " -----------"}, + {"sim", CmdLFHitagUSim, IfPm3Hitag, "Simulate Hitag µ transponder"}, + {NULL, NULL, 0, NULL} }; static int CmdHelp(const char *Cmd) { diff --git a/doc/commands.json b/doc/commands.json index 31527f680..276bcfbd8 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -9977,7 +9977,7 @@ }, "lf hitag help": { "command": "lf hitag help", - "description": "help This help list List Hitag trace history hts { Hitag S/8211 operations } htu { Hitag \u00b5/8265 operations } test Perform self tests view Display content from tag dump file lookup Uses authentication trace to check for key in dictionary file --------------------------------------------------------------------------------------- lf hitag list available offline: yes Alias of `trace list -t hitag2` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List Hitag trace history hts { Hitag S/8211 operations } htu { Hitag \u00b5/8265 operations } test Perform self tests view Display content from tag dump file lookup Uses authentication trace to check for key in dictionary file --------------------------------------------------------------------------------------- lf hitag list available offline: yes Alias of `trace list -t ht2` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "lf hitag list --frame -> show frame delay times", "lf hitag list -1 -> use trace buffer" @@ -10021,7 +10021,7 @@ }, "lf hitag hts help": { "command": "lf hitag hts help", - "description": "help This help list List Hitag S trace history --------------------------------------------------------------------------------------- lf hitag hts list available offline: yes Alias of `trace list -t hitags` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List Hitag S trace history --------------------------------------------------------------------------------------- lf hitag hts list available offline: yes Alias of `trace list -t hts` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "lf hitag hts list --frame -> show frame delay times", "lf hitag hts list -1 -> use trace buffer" @@ -10158,7 +10158,7 @@ }, "lf hitag htu help": { "command": "lf hitag htu help", - "description": "help This help list List Hitag \u00b5 trace history --------------------------------------------------------------------------------------- lf hitag htu list available offline: yes Alias of `trace list -t hitagu` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List Hitag \u00b5 trace history --------------------------------------------------------------------------------------- lf hitag htu list available offline: yes Alias of `trace list -t htu` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "lf hitag htu list --frame -> show frame delay times", "lf hitag htu list -1 -> use trace buffer" @@ -13357,6 +13357,6 @@ "metadata": { "commands_extracted": 767, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-19T14:19:20" + "extracted_on": "2025-03-19T20:08:46" } } From 17a8f0f9c2ab0628cda3dc81a7e673012509977f Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 20 Mar 2025 12:17:37 +0100 Subject: [PATCH 41/43] text --- client/src/cmddata.c | 2 +- client/src/cmdflashmem.c | 4 ++-- client/src/cmdflashmemspiffs.c | 6 +++--- client/src/cmdhf.c | 2 +- client/src/cmdhf14a.c | 10 +++++----- client/src/cmdhf15.c | 2 +- client/src/cmdhffelica.c | 4 ++-- client/src/cmdhficlass.c | 6 +++--- client/src/cmdhflto.c | 2 +- client/src/cmdhfmf.c | 2 +- client/src/cmdhfmfdes.c | 2 +- client/src/cmdhfmfp.c | 2 +- client/src/cmdhfmfu.c | 4 ++-- client/src/cmdhfthinfilm.c | 2 +- client/src/cmdhftopaz.c | 2 +- client/src/cmdlf.c | 2 +- client/src/cmdlfem410x.c | 6 +++--- client/src/cmdlfem4x05.c | 6 +++--- client/src/cmdlfem4x50.c | 14 +++++++------- client/src/cmdlfem4x70.c | 24 ++++++++++++------------ client/src/cmdlfhid.c | 2 +- client/src/cmdlfhitag.c | 16 ++++++++-------- client/src/cmdlfhitaghts.c | 14 +++++++------- client/src/cmdlfhitagu.c | 2 +- client/src/cmdsmartcard.c | 4 ++-- client/src/cmdtrace.c | 2 +- client/src/mifare/desfirecore.c | 2 +- 27 files changed, 73 insertions(+), 73 deletions(-) diff --git a/client/src/cmddata.c b/client/src/cmddata.c index b0765ae0b..f20b9e670 100644 --- a/client/src/cmddata.c +++ b/client/src/cmddata.c @@ -1866,7 +1866,7 @@ int getSamplesEx(uint32_t start, uint32_t end, bool verbose, bool ignore_lf_conf PacketResponseNG resp; if (GetFromDevice(BIG_BUF, got, n, start, NULL, 0, &resp, 10000, true) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdflashmem.c b/client/src/cmdflashmem.c index dbb030bf8..f2237cf2a 100644 --- a/client/src/cmdflashmem.c +++ b/client/src/cmdflashmem.c @@ -349,7 +349,7 @@ static int CmdFlashMemLoad(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_FLASHMEM_WRITE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); g_conn.block_after_ACK = false; free(data); return PM3_ETIMEOUT; @@ -473,7 +473,7 @@ static int CmdFlashMemWipe(const char *Cmd) { SendCommandMIX(CMD_FLASHMEM_WIPE, page, initialwipe, 0, NULL, 0); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_ACK, &resp, 10000)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdflashmemspiffs.c b/client/src/cmdflashmemspiffs.c index fef35bb4d..4f8cf90d6 100644 --- a/client/src/cmdflashmemspiffs.c +++ b/client/src/cmdflashmemspiffs.c @@ -69,7 +69,7 @@ int flashmem_spiffs_load(const char *destfn, const uint8_t *data, size_t datalen uint8_t retry = 3; while (WaitForResponseTimeout(CMD_SPIFFS_WRITE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); retry--; if (retry == 0) { ret_val = PM3_ETIMEOUT; @@ -96,7 +96,7 @@ int flashmem_spiffs_download(char *fn, uint8_t fnlen, void **pdest, size_t *dest SendCommandNG(CMD_SPIFFS_STAT, (uint8_t *)fn, fnlen); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_SPIFFS_STAT, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -401,7 +401,7 @@ static int CmdFlashMemSpiFFSDump(const char *Cmd) { SendCommandNG(CMD_SPIFFS_STAT, (uint8_t *)src, slen); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_SPIFFS_STAT, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index ae5f8b845..350299211 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -526,7 +526,7 @@ int handle_hf_plot(bool show_plot) { PacketResponseNG resp; if (GetFromDevice(FPGA_MEM, buf, FPGA_TRACE_SIZE, 0, NULL, 0, &resp, 4000, true) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 1a694bc49..201cc95da 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -498,7 +498,7 @@ int Hf14443_4aGetCardData(iso14a_card_select_t *card) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -806,7 +806,7 @@ static int CmdHF14ACUIDs(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1670,7 +1670,7 @@ static int waitCmd(bool i_select, uint32_t timeout, bool verbose) { } } else { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } return PM3_SUCCESS; @@ -2264,7 +2264,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); DropField(); return PM3_ETIMEOUT; } @@ -2324,7 +2324,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, sizeof(rats)); if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index fa5fb266a..7a3875e00 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1182,7 +1182,7 @@ static void hf15EmlClear(void) { SendCommandNG(CMD_HF_ISO15693_EML_CLEAR, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_HF_ISO15693_EML_CLEAR, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } } diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index be0fe2e62..22cf5eb41 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -264,7 +264,7 @@ static const char *felica_model_name(uint8_t rom_type, uint8_t ic_type) { */ static bool waitCmdFelica(bool iSelect, PacketResponseNG *resp, bool verbose) { if (WaitForResponseTimeout(CMD_ACK, resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return false; } @@ -2068,7 +2068,7 @@ static int CmdHFFelicaDumpLite(const char *Cmd) { return PM3_EOPABORTED; } if (timeout > 10) { - PrintAndLogEx(WARNING, "\ntimeout while waiting for reply."); + PrintAndLogEx(WARNING, "\ntimeout while waiting for reply"); DropField(); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 885c27c1d..302f9d9ce 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -922,7 +922,7 @@ static int CmdHFiClassSim(const char *Cmd) { return PM3_EOPABORTED; } if (tries > 20) { - PrintAndLogEx(WARNING, "\ntimeout while waiting for reply."); + PrintAndLogEx(WARNING, "\ntimeout while waiting for reply"); return PM3_ETIMEOUT; } } @@ -973,7 +973,7 @@ static int CmdHFiClassSim(const char *Cmd) { return PM3_EOPABORTED; } if (tries > 20) { - PrintAndLogEx(WARNING, "\ntimeout while waiting for reply."); + PrintAndLogEx(WARNING, "\ntimeout while waiting for reply"); return PM3_ETIMEOUT; } } @@ -1186,7 +1186,7 @@ static int CmdHFiClassELoad(const char *Cmd) { SendCommandNG(CMD_SPIFFS_ELOAD, (uint8_t *)filename, fnlen); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_SPIFFS_ELOAD, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhflto.c b/client/src/cmdhflto.c index 7542114b1..f68dcdd5e 100644 --- a/client/src/cmdhflto.c +++ b/client/src/cmdhflto.c @@ -132,7 +132,7 @@ static int lto_send_cmd_raw(uint8_t *cmd, uint8_t len, uint8_t *response, uint16 PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - if (verbose) PrintAndLogEx(WARNING, "timeout while waiting for reply."); + if (verbose) PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index be3eb80ec..0f23b7922 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -4713,7 +4713,7 @@ int CmdHF14AMfELoad(const char *Cmd) { SendCommandNG(CMD_SPIFFS_ELOAD, (uint8_t *)filename, fnlen); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_SPIFFS_ELOAD, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 123760fcd..1b6be4145 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -1446,7 +1446,7 @@ static int CmdHF14aDesChk(const char *Cmd) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 37e76a0f7..bb4bd28ce 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1655,7 +1655,7 @@ static int CmdHFMFPChk(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 9ef064824..40f546741 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -315,7 +315,7 @@ int ul_read_uid(uint8_t *uid) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_RATS, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } iso14a_card_select_t card; @@ -4231,7 +4231,7 @@ static int CmdHF14AMfUKeyGen(const char *Cmd) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_RATS, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhfthinfilm.c b/client/src/cmdhfthinfilm.c index 6a477e5f6..4e3bafdb9 100644 --- a/client/src/cmdhfthinfilm.c +++ b/client/src/cmdhfthinfilm.c @@ -126,7 +126,7 @@ int infoThinFilm(bool verbose) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_HF_THINFILM_READ, &resp, 1500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdhftopaz.c b/client/src/cmdhftopaz.c index dba9d65c4..d7205c784 100644 --- a/client/src/cmdhftopaz.c +++ b/client/src/cmdhftopaz.c @@ -54,7 +54,7 @@ static int topaz_send_cmd_raw(uint8_t *cmd, uint8_t len, uint8_t *response, uint SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_NO_DISCONNECT | ISO14A_TOPAZMODE | ISO14A_NO_RATS, len, 0, cmd, len); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { - if (verbose) PrintAndLogEx(WARNING, "timeout while waiting for reply."); + if (verbose) PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index 7bf4dd553..a320ca57b 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -456,7 +456,7 @@ int CmdLFCommandRead(const char *Cmd) { getSamples(samples, false); ret = PM3_SUCCESS; } else { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index daaad2acc..c56a22e8c 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -756,7 +756,7 @@ static int CmdEM410xClone(const char *Cmd) { packet.mode = HITAGS_UID_REQ_FADV; SendCommandNG(CMD_LF_HITAGS_WRITE, (uint8_t *)&packet, sizeof(packet)); if (WaitForResponseTimeout(CMD_LF_HITAGS_WRITE, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { @@ -814,7 +814,7 @@ static int CmdEM410xClone(const char *Cmd) { SendCommandNG(CMD_LF_HITAGU_WRITE, (uint8_t *)&packet, sizeof(packet)); if (WaitForResponseTimeout(CMD_LF_HITAGU_WRITE, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -851,7 +851,7 @@ static int CmdEM410xClone(const char *Cmd) { SendCommandNG(CMD_LF_EM410X_CLONE, (uint8_t *)&payload, sizeof(payload)); if (WaitForResponseTimeout(CMD_LF_EM410X_CLONE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } } diff --git a/client/src/cmdlfem4x05.c b/client/src/cmdlfem4x05.c index 2a1fcf387..ee1cacaa0 100644 --- a/client/src/cmdlfem4x05.c +++ b/client/src/cmdlfem4x05.c @@ -469,7 +469,7 @@ static int em4x05_login_ext(uint32_t pwd) { SendCommandNG(CMD_LF_EM4X_LOGIN, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X_LOGIN, &resp, 10000) == false) { - PrintAndLogEx(WARNING, "(em4x05_login_ext) timeout while waiting for reply."); + PrintAndLogEx(WARNING, "(em4x05_login_ext) timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -496,7 +496,7 @@ int em4x05_read_word_ext(uint8_t addr, uint32_t pwd, bool use_pwd, uint32_t *wor SendCommandNG(CMD_LF_EM4X_READWORD, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X_READWORD, &resp, 10000) == false) { - PrintAndLogEx(WARNING, "(em4x05_read_word_ext) timeout while waiting for reply."); + PrintAndLogEx(WARNING, "(em4x05_read_word_ext) timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1839,7 +1839,7 @@ int CmdEM4x05Brute(const char *Cmd) { SendCommandNG(CMD_LF_EM4X_BF, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X_BF, &resp, 1000) == false) { - PrintAndLogEx(WARNING, "(EM4x05 Bruteforce) timeout while waiting for reply."); + PrintAndLogEx(WARNING, "(EM4x05 Bruteforce) timeout while waiting for reply"); return PM3_ETIMEOUT; } PrintAndLogEx(INFO, "Bruteforce is running on device side, press button to interrupt"); diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index bda13661f..db0a1ea53 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -341,7 +341,7 @@ static int CmdEM4x50Login(const char *Cmd) { PacketResponseNG resp; SendCommandNG(CMD_LF_EM4X50_LOGIN, (uint8_t *)&password, sizeof(password)); if (WaitForResponseTimeout(CMD_LF_EM4X50_LOGIN, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -631,7 +631,7 @@ int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out) { SendCommandNG(CMD_LF_EM4X50_READ, (uint8_t *)&edata, sizeof(edata)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X50_READ, &resp, EM4X50_TIMEOUT_CMD) == false) { - PrintAndLogEx(WARNING, "(em4x50) timeout while waiting for reply."); + PrintAndLogEx(WARNING, "(em4x50) timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -740,7 +740,7 @@ static int CmdEM4x50Info(const char *Cmd) { SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, EM4X50_TIMEOUT_CMD) == false) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -962,7 +962,7 @@ static int CmdEM4x50Write(const char *Cmd) { SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, EM4X50_TIMEOUT_CMD) == false) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1033,7 +1033,7 @@ static int CmdEM4x50WritePwd(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITEPWD, (uint8_t *)&etd, sizeof(etd)); if (WaitForResponseTimeout(CMD_LF_EM4X50_WRITEPWD, &resp, EM4X50_TIMEOUT_CMD) == false) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1117,7 +1117,7 @@ static int CmdEM4x50Wipe(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); if (WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, EM4X50_TIMEOUT_CMD) == false) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1213,7 +1213,7 @@ static int CmdEM4x50Restore(const char *Cmd) { SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); if (WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, EM4X50_TIMEOUT_CMD) == false) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index bfd7529fe..b1869cbc8 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -478,7 +478,7 @@ static int CmdEM4x70Info(const char *Cmd) { int result = get_em4x70_info(&opts, &info); if (result == PM3_ETIMEOUT) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } else if (result == PM3_SUCCESS) { em4x70_print_info_result(&info); } else { @@ -530,7 +530,7 @@ static int CmdEM4x70Write(const char *Cmd) { int result = writeblock_em4x70(&opts, &info); if (result == PM3_ETIMEOUT) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } else if (result == PM3_SUCCESS) { em4x70_print_info_result(&info); } else { @@ -664,7 +664,7 @@ static int CmdEM4x70Unlock(const char *Cmd) { int result = unlock_em4x70(&opts, &info); if (result == PM3_ETIMEOUT) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } else if (result == PM3_SUCCESS) { em4x70_print_info_result(&info); } else { @@ -726,7 +726,7 @@ static int CmdEM4x70Auth(const char *Cmd) { if (PM3_SUCCESS == result) { PrintAndLogEx(INFO, "Tag Auth Response: %02X %02X %02X", data.grn.grn[0], data.grn.grn[1], data.grn.grn[2]); } else if (PM3_ETIMEOUT == result) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } else { PrintAndLogEx(FAILED, "TAG Authentication ( " _RED_("fail") " )"); } @@ -768,7 +768,7 @@ static int CmdEM4x70SetPIN(const char *Cmd) { int result = setpin_em4x70(&opts, &info); if (result == PM3_ETIMEOUT) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } else if (result == PM3_SUCCESS) { em4x70_print_info_result(&info); PrintAndLogEx(INFO, "Writing new PIN ( " _GREEN_("ok") " )"); @@ -812,7 +812,7 @@ static int CmdEM4x70SetKey(const char *Cmd) { int result = setkey_em4x70(&opts); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } else if (PM3_SUCCESS != result) { PrintAndLogEx(FAILED, "Writing new key " _RED_("fail")); @@ -857,7 +857,7 @@ static int CmdEM4x70SetKey(const char *Cmd) { result = verify_auth_em4x70(&opts_v); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return result; } else if (PM3_SUCCESS != result) { PrintAndLogEx(FAILED, "Authenticating with new key ( " _RED_("fail") " )"); @@ -1204,7 +1204,7 @@ static int CmdEM4x70AutoRecover(const char *Cmd) { result = auth_em4x70(&opts_auth, &tag_grn); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return result; } else if (PM3_SUCCESS != result) { PrintAndLogEx(FAILED, "Authenticating with provided values ( " _RED_("fail") " )"); @@ -1248,7 +1248,7 @@ static int CmdEM4x70AutoRecover(const char *Cmd) { result = writeblock_em4x70(&opt_write_zeros, &tag_info); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(FAILED, "Timeout while waiting for reply."); + PrintAndLogEx(FAILED, "timeout while waiting for reply"); PrintAndLogEx(HINT, "Block %d data may have been overwritten. Manually restart at step %d", block, step); return result; } else if (PM3_SUCCESS != result) { @@ -1273,7 +1273,7 @@ static int CmdEM4x70AutoRecover(const char *Cmd) { result = brute_em4x70(&opts_brute, &brute); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(FAILED, "Timeout while waiting for reply."); + PrintAndLogEx(FAILED, "timeout while waiting for reply"); PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d", block, step); return result; } else if (PM3_SUCCESS != result) { @@ -1312,7 +1312,7 @@ static int CmdEM4x70AutoRecover(const char *Cmd) { result = writeblock_em4x70(&opt_write_zeros, &tag_info); if (PM3_ETIMEOUT == result) { - PrintAndLogEx(FAILED, "Timeout while waiting for reply."); + PrintAndLogEx(FAILED, "timeout while waiting for reply"); PrintAndLogEx(HINT, "Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten", block, brute.partial_key[0], brute.partial_key[1]); return result; } else if (PM3_SUCCESS != result) { @@ -1578,7 +1578,7 @@ bool detect_4x70_block(void) { int result = get_em4x70_info(&opts, &info); if (result == PM3_ETIMEOUT) { // consider removing this output? - PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); } return result == PM3_SUCCESS; } diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index 6b651dccc..a1631c0b7 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -473,7 +473,7 @@ static int CmdHIDClone(const char *Cmd) { SendCommandNG(CMD_LF_HID_CLONE, (uint8_t *)&payload, sizeof(payload)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HID_CLONE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 8e49802aa..9ebe97891 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -520,7 +520,7 @@ static int ht2_check_dictionary(uint32_t key_count, uint8_t *keys, uint8_t keyl SendCommandNG(CMD_LF_HITAG_READER, (uint8_t *)&packet, sizeof(packet)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAG_READER, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); SendCommandNG(CMD_BREAK_LOOP, NULL, 0); return PM3_ETIMEOUT; } @@ -825,7 +825,7 @@ static bool ht2_get_uid(uint32_t *uid) { SendCommandNG(CMD_LF_HITAG_READER, (uint8_t *) &packet, sizeof(packet)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAG_READER, &resp, 1500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return false; } @@ -1052,7 +1052,7 @@ static int CmdLFHitagRd(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(pm3cmd, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); SendCommandNG(CMD_BREAK_LOOP, NULL, 0); return PM3_ETIMEOUT; } @@ -1141,7 +1141,7 @@ static int CmdLFHitag2CheckChallenges(const char *Cmd) { SendCommandNG(CMD_LF_HITAG_READER, (uint8_t *)&packet, sizeof(packet)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAG_READER, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { @@ -1303,7 +1303,7 @@ static int CmdLFHitagWriter(const char *Cmd) { SendCommandNG(CMD_LF_HITAG2_WRITE, (uint8_t *)&packet, sizeof(packet)); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAG2_WRITE, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -1526,7 +1526,7 @@ static int CmdLFHitag2Dump(const char *Cmd) { if (attempt == 0) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ESOFT; } @@ -1544,7 +1544,7 @@ static int CmdLFHitag2Dump(const char *Cmd) { SendCommandNG(CMD_LF_HITAG_READER, (uint8_t *) &packet, sizeof(packet)); if (WaitForResponseTimeout(CMD_LF_HITAG_READER, &resp, 5000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { @@ -2254,7 +2254,7 @@ static int CmdLFHitag2Crack2(const char *Cmd) { if (attempt == 0) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ESOFT; } diff --git a/client/src/cmdlfhitaghts.c b/client/src/cmdlfhitaghts.c index af4fe9978..536e62bbf 100644 --- a/client/src/cmdlfhitaghts.c +++ b/client/src/cmdlfhitaghts.c @@ -161,7 +161,7 @@ static bool hts_get_uid(uint32_t *uid) { SendCommandNG(CMD_LF_HITAGS_UID, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGS_UID, &resp, 1500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return false; } @@ -420,7 +420,7 @@ static int CmdLFHitagSRead(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGS_READ, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); SendCommandNG(CMD_BREAK_LOOP, NULL, 0); return PM3_ETIMEOUT; } @@ -607,7 +607,7 @@ static int CmdLFHitagSDump(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGS_READ, &resp, 5000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } @@ -709,7 +709,7 @@ static int CmdLFHitagSRestore(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGS_READ, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); free(dump); return PM3_ETIMEOUT; } @@ -760,7 +760,7 @@ static int CmdLFHitagSRestore(const char *Cmd) { SendCommandNG(CMD_LF_HITAGS_WRITE, (uint8_t *)&packet, sizeof(packet)); if (WaitForResponseTimeout(CMD_LF_HITAGS_WRITE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); free(dump); return PM3_ETIMEOUT; } @@ -833,7 +833,7 @@ static int CmdLFHitagSRestore(const char *Cmd) { SendCommandNG(CMD_LF_HITAGS_WRITE, (uint8_t *)&packet, sizeof(packet)); if (WaitForResponseTimeout(CMD_LF_HITAGS_WRITE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); free(dump); return PM3_ETIMEOUT; } @@ -918,7 +918,7 @@ static int CmdLFHitagSWrite(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGS_WRITE, &resp, 4000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c index 3758415ed..54b318bd7 100644 --- a/client/src/cmdlfhitagu.c +++ b/client/src/cmdlfhitagu.c @@ -176,7 +176,7 @@ static bool htu_get_uid(uint64_t *uid) { SendCommandNG(CMD_LF_HITAGU_UID, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_LF_HITAGU_UID, &resp, 1500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return false; } diff --git a/client/src/cmdsmartcard.c b/client/src/cmdsmartcard.c index aa27fe6d4..13f044320 100644 --- a/client/src/cmdsmartcard.c +++ b/client/src/cmdsmartcard.c @@ -658,7 +658,7 @@ static int CmdSmartUpgrade(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_SMART_UPLOAD, (uint8_t *)&upload, sizeof(upload)); if (!WaitForResponseTimeout(CMD_SMART_UPLOAD, &resp, 2000)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); free(firmware); return PM3_ETIMEOUT; } @@ -690,7 +690,7 @@ static int CmdSmartUpgrade(const char *Cmd) { free(firmware); SendCommandNG(CMD_SMART_UPGRADE, (uint8_t *)&payload, sizeof(payload)); if (!WaitForResponseTimeout(CMD_SMART_UPGRADE, &resp, 2500)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 2cc28446b..c217f3e80 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -1112,7 +1112,7 @@ static int download_trace(void) { // Query for the size of the trace, downloading PM3_CMD_DATA_SIZE PacketResponseNG resp; if (!GetFromDevice(BIG_BUF, gs_trace, PM3_CMD_DATA_SIZE, 0, NULL, 0, &resp, 4000, true)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); free(gs_trace); gs_trace = NULL; return PM3_ETIMEOUT; diff --git a/client/src/mifare/desfirecore.c b/client/src/mifare/desfirecore.c index f390c3ca2..83d29ecf7 100644 --- a/client/src/mifare/desfirecore.c +++ b/client/src/mifare/desfirecore.c @@ -3073,7 +3073,7 @@ int DesfireGetCardUID(DesfireContext_t *ctx) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "timeout while waiting for reply"); return PM3_ETIMEOUT; } From 93c625089435d80c4d80639d966ed1bd714398f8 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 20 Mar 2025 12:57:54 +0100 Subject: [PATCH 42/43] fix #2796 --- armsrc/hitagu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c index 77549aa2d..30816f5af 100644 --- a/armsrc/hitagu.c +++ b/armsrc/hitagu.c @@ -672,7 +672,7 @@ int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer) { DBG Dbprintf("HitagU UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); if (uid) { - *uid = BSWAP_48(*(uint64_t *)&tag.uid); + *uid = MemBeToUint6byte(tag.uid); } exit: From 2c9a74b906ebe185842ace0dad64f88c32021a2f Mon Sep 17 00:00:00 2001 From: douniwan5788 Date: Thu, 20 Mar 2025 21:46:34 +0800 Subject: [PATCH 43/43] =?UTF-8?q?Improve=20Hitag=20=C2=B5=20error=20reason?= =?UTF-8?q?=20and=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- armsrc/hitagu.c | 14 +++++++------- client/src/cmdlfhitagu.c | 28 ++++++---------------------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c index 30816f5af..897439f7c 100644 --- a/armsrc/hitagu.c +++ b/armsrc/hitagu.c @@ -540,7 +540,7 @@ static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t si // Check if the response is valid if (rxlen < 1 + 48 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { DBG Dbprintf("Read UID command failed! %i", rxlen); - return -1; // Read UID failed + return -2; // Read UID failed } // Process the UID from the response @@ -565,7 +565,7 @@ static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t si if (rxlen < 1 + 16 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { // 8265 bug? sometile lost Data field first bit DBG Dbprintf("Get System Information command failed! %i", rxlen); - return -2; // Get System Information failed + return -3; // Get System Information failed } // 3. Read config block @@ -590,7 +590,7 @@ static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t si // Check if the response is valid if (rxlen < 1 + 32 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { DBG Dbprintf("Read config block command failed! %i", rxlen); - return -2; // Read config block failed + return -3; // Read config block failed } // Process the config block from the response @@ -616,7 +616,7 @@ static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t si // Check if login succeeded if (rxlen < 1 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { DBG Dbprintf("Login command failed! %i", rxlen); - return -3; // Login failed + return -4; // Login failed } else { DBG DbpString("Login successful"); } @@ -772,14 +772,14 @@ void htu_read(const lf_hitag_data_t *payload, bool ledcontrol) { if (flags & HITAGU_FLAG_CRCT && Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { DBG Dbprintf("Error: response CRC invalid"); - card.pages_reason[i] = -1; + card.pages_reason[i] = -6; continue; } // Check response if (rxlen < 1 + HITAGU_BLOCK_SIZE * 8 + (flags & HITAGU_FLAG_CRCT ? 16 : 0)) { DbpString("Error: invalid response received after read command"); - card.pages_reason[i] = -11; + card.pages_reason[i] = -7; } else { DBG Dbprintf("Read successful, response: %d bits", rxlen); // todo: For certain pages, update our cached data @@ -885,7 +885,7 @@ void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { status = PM3_ENODATA; } else if (rxlen != 1 + 16) { DbpString("Error: htu_write_page No valid response received after write command"); - reason = -1; + reason = -5; status = PM3_ERFTRANS; } else { DBG Dbprintf("Write successful, response: %d bits", rxlen); diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c index 54b318bd7..da7fb34d0 100644 --- a/client/src/cmdlfhitagu.c +++ b/client/src/cmdlfhitagu.c @@ -253,46 +253,30 @@ static void print_error(int8_t reason) { //todo: USE ENUM OR DEFINES switch (reason) { case 0: { - PrintAndLogEx(FAILED, "No data"); + PrintAndLogEx(INFO, "No data"); break; } case -2: { - PrintAndLogEx(FAILED, "UID Request failed!"); + PrintAndLogEx(FAILED, "READ UID failed!"); break; } case -3: { - PrintAndLogEx(FAILED, "Select UID failed!"); + PrintAndLogEx(FAILED, "Get System Information / Config failed!"); break; } case -4: { - PrintAndLogEx(FAILED, "No write access on block. Not authorized?"); + PrintAndLogEx(FAILED, "Login failed! Wrong password?"); break; } case -5: { - PrintAndLogEx(FAILED, "Write failed! Wrong password?"); + PrintAndLogEx(FAILED, "No write access on block. Not authorized?"); break; } case -6: { - PrintAndLogEx(FAILED, "Error, " _YELLOW_("AUT=1") " This tag is configured in Authentication Mode"); + PrintAndLogEx(FAILED, "Response CRC invalid!"); break; } case -7: { - PrintAndLogEx(FAILED, "Error, unknown function"); - break; - } - case -8: { - PrintAndLogEx(FAILED, "Authenticate failed!"); - break; - } - case -9: { - PrintAndLogEx(FAILED, "No write access on block"); - break; - } - case -10: { - PrintAndLogEx(FAILED, "Write to block failed!"); - break; - } - case -11: { PrintAndLogEx(FAILED, "Read block failed!"); break; }