From 21ad101ff522716a6f2a171f28e0772f2b293bd8 Mon Sep 17 00:00:00 2001 From: Henry Gabryjelski Date: Sat, 15 Mar 2025 23:34:01 -0700 Subject: [PATCH] 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__ */