diff --git a/CHANGELOG.md b/CHANGELOG.md index 7510597a5..4dcffb995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab) - Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) - Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo) - Changed `trace list -t seos` - improved annotation (@iceman1001) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 547ce5ce2..1e1b59347 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,63 +464,11 @@ 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) @@ -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; } @@ -527,16 +1229,18 @@ static bool find_listen_window(bool command) { 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))) { + 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/armsrc/hitag2.c b/armsrc/hitag2.c index a8f04e8a2..39e4647e0 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -320,7 +320,7 @@ static void hitag2_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_ // reader/writer // returns how long it took -static uint32_t hitag_reader_send_bit(int bit) { +static uint32_t hitag2_reader_send_bit(int bit) { // Binary pulse length modulation (BPLM) is used to encode the data stream // This means that a transmission of a one takes longer than that of a zero @@ -349,13 +349,13 @@ static uint32_t hitag_reader_send_bit(int bit) { // reader / writer commands // frame_len is in number of bits? -static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) { +static uint32_t hitag2_reader_send_frame(const uint8_t *frame, size_t frame_len) { WDT_HIT(); uint32_t wait = 0; // Send the content of the frame for (size_t i = 0; i < frame_len; i++) { - wait += hitag_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1); + wait += hitag2_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1); } // Send EOF @@ -378,14 +378,14 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) // reader / writer commands // frame_len is in number of bits? -static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_len) { +static uint32_t hitag2_reader_send_framebits(const uint8_t *frame, size_t frame_len) { WDT_HIT(); uint32_t wait = 0; // Send the content of the frame for (size_t i = 0; i < frame_len; i++) { - wait += hitag_reader_send_bit(frame[i]); + wait += hitag2_reader_send_bit(frame[i]); } // Send EOF @@ -1863,7 +1863,7 @@ void ReaderHitag(const lf_hitag_data_t *payload, bool ledcontrol) { } // Transmit the reader frame - command_duration = hitag_reader_send_frame(tx, txlen); + command_duration = hitag2_reader_send_frame(tx, txlen); response_start = command_start + command_duration; // Let the antenna and ADC values settle @@ -2214,7 +2214,7 @@ void WriterHitag(const lf_hitag_data_t *payload, bool ledcontrol) { } // Transmit the reader frame - command_duration = hitag_reader_send_frame(tx, txlen); + command_duration = hitag2_reader_send_frame(tx, txlen); // global write state variable used // tearoff occurred @@ -2434,9 +2434,9 @@ static void ht2_send(bool turn_on, uint32_t *cmd_start // Transmit the reader frame if (send_bits) { - *cmd_duration = hitag_reader_send_framebits(tx, txlen); + *cmd_duration = hitag2_reader_send_framebits(tx, txlen); } else { - *cmd_duration = hitag_reader_send_frame(tx, txlen); + *cmd_duration = hitag2_reader_send_frame(tx, txlen); } *resp_start = (*cmd_start + *cmd_duration); diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 4c4e3d493..7a42fcd81 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -419,7 +419,7 @@ static void hts_init_clock(void) { static int check_select(const uint8_t *rx, uint32_t uid) { // global var? - concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32); + concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32, false); reader_selected_uid = BSWAP_32(reader_selected_uid); if (reader_selected_uid == uid) { @@ -1090,7 +1090,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz protocol_mode = packet->mode; uint8_t cmd = protocol_mode; - txlen = concatbits(tx, txlen, &cmd, 0, 5); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, t_wait, ledcontrol, true); if (rxlen != 32) { @@ -1105,10 +1105,10 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz // select uid txlen = 0; cmd = HITAGS_SELECT; - txlen = concatbits(tx, txlen, &cmd, 0, 5); - txlen = concatbits(tx, txlen, rx, 0, 32); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); + txlen = concatbits(tx, txlen, rx, 0, 32, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1140,8 +1140,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz } txlen = 0; - txlen = concatbits(tx, txlen, rnd, 0, 32); - txlen = concatbits(tx, txlen, auth_ks, 0, 32); + txlen = concatbits(tx, txlen, rnd, 0, 32, false); + txlen = concatbits(tx, txlen, auth_ks, 0, 32, false); DBG DbpString("Authenticating using key:"); DBG Dbhexdump(6, packet->key, false); @@ -1173,13 +1173,13 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz // send write page request txlen = 0; cmd = HITAGS_WRITE_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = 64; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1189,9 +1189,9 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz } txlen = 0; - txlen = concatbits(tx, txlen, packet->pwd, 0, 32); + txlen = concatbits(tx, txlen, packet->pwd, 0, 32, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1287,11 +1287,11 @@ void hts_read(const lf_hitag_data_t *payload, bool ledcontrol) { //send read request size_t txlen = 0; uint8_t cmd = HITAGS_READ_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = page_addr; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1396,13 +1396,13 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { txlen = 0; uint8_t cmd = HITAGS_WRITE_PAGE; - txlen = concatbits(tx, txlen, &cmd, 0, 4); + txlen = concatbits(tx, txlen, &cmd, 0, 4, false); uint8_t addr = payload->page; - txlen = concatbits(tx, txlen, &addr, 0, 8); + txlen = concatbits(tx, txlen, &addr, 0, 8, false); uint8_t crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false); @@ -1430,9 +1430,9 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { // } txlen = 0; - txlen = concatbits(tx, txlen, payload->data, 0, 32); + txlen = concatbits(tx, txlen, payload->data, 0, 32, false); crc = CRC8Hitag1Bits(tx, txlen); - txlen = concatbits(tx, txlen, &crc, 0, 8); + txlen = concatbits(tx, txlen, &crc, 0, 8, false); enable_page_tearoff = g_tearoff_enabled; @@ -1493,7 +1493,7 @@ int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) { size_t txlen = 0; uint8_t tx[HITAG_FRAME_LEN] = { 0x00 }; - txlen = concatbits(tx, txlen, &cmd, 0, 5); + txlen = concatbits(tx, txlen, &cmd, 0, 5, false); hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_FIRST, ledcontrol, true); diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index c1f2e3f7f..8eefd354e 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -43,7 +43,7 @@ #define _64T0 (CLOCK) // calculating the two possible pmc lengths, based on the clock. -4 at the end is to make sure not to increment too far -#define PMC_16T0_LEN ((128 + 127 + 16 + 32 + 33 + 16) * CLOCK/64); +#define PMC_16T0_LEN ((128 + 127 + 16 + 32 + 33 + 16) * CLOCK/64); #define PMC_32T0_LEN ((128 + 127 + 16 + 32 + 33 ) * CLOCK/64); // theshold for recognition of positive/negative slope @@ -54,13 +54,13 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { uint8_t blocks[8][16]; uint8_t *dest = BigBuf_get_addr(); uint16_t g_GraphTraceLen = BigBuf_max_traceLen(); - // limit g_GraphTraceLen to a little more than 2 data frames. + // limit g_GraphTraceLen to a little more than 2 data frames. // To make sure a complete dataframe is in the dataset. // 1 Frame is 16 Byte -> 128byte. at a T0 of 64 -> 8129 Samples per frame. // + PMC -> 384T0 --> 8576 samples required for one block // to make sure that one complete block is definitely being sampled, we need 2 times that // which is ~17.xxx samples. round up. and clamp to this value. - + // TODO: Doublecheck why this is being limited? - seems not to be needed. // g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; @@ -87,15 +87,15 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { samplePosLastEdge = 0; block_done = 0; bitPos = 0; - lastClockDuration=0; - - for (sample = 1 ; sample < g_GraphTraceLen-4; sample++) { - // condition is searching for the next edge, in the expected diretion. - //todo: without flouz - dest[sample] = (uint8_t)(dest[sample-1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter + lastClockDuration = 0; - if ( ((dest[sample] + THRESHOLD) < dest[sample-1] && expectedNextEdge == FALLING ) || - ((dest[sample] - THRESHOLD) > dest[sample-1] && expectedNextEdge == RISING )) { + for (sample = 1 ; sample < g_GraphTraceLen - 4; sample++) { + // condition is searching for the next edge, in the expected diretion. + //todo: without flouz + dest[sample] = (uint8_t)(dest[sample - 1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter + + if (((dest[sample] + THRESHOLD) < dest[sample - 1] && expectedNextEdge == FALLING) || + ((dest[sample] - THRESHOLD) > dest[sample - 1] && expectedNextEdge == RISING)) { //okay, next falling/rising edge found expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge @@ -104,14 +104,14 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { lastClockDuration = samplePosCurrentEdge - samplePosLastEdge; samplePosLastEdge = sample; - // Dbprintf("%d, %d, edge found, len: %d, nextEdge: %d", sample, dest[sample], lastClockDuration*DECIMATION, expectedNextEdge); - + // Dbprintf("%d, %d, edge found, len: %d, nextEdge: %d", sample, dest[sample], lastClockDuration*DECIMATION, expectedNextEdge); + // Switch depending on lastClockDuration length: - // 16T0 + // 16T0 if (ABS(lastClockDuration - _16T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! - if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { + if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC Dbprintf(_GREEN_("PMC 16T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); sample += PMC_16T0_LEN; // move to the sample after PMC @@ -120,44 +120,44 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { samplePosLastEdge = sample; block_done = 1; } - - // 32TO + + // 32TO } else if (ABS(lastClockDuration - _32T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC ! Dbprintf(_GREEN_("PMC 32T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - + sample += PMC_32T0_LEN; // move to the sample after PMC expectedNextEdge = FALLING; samplePosLastEdge = sample; block_done = 1; - // if no pmc, then its a normal bit. - // Check if its the second time, the edge changed if yes, then the bit is 0 + // if no pmc, then its a normal bit. + // Check if its the second time, the edge changed if yes, then the bit is 0 } else if (half_switch == 1) { bits[bitPos] = 0; // reset the edge counter to 0 half_switch = 0; bitPos++; - // if it is the first time the edge changed. No bit value will be set here, bit if the - // edge changes again, it will be. see case above. + // if it is the first time the edge changed. No bit value will be set here, bit if the + // edge changes again, it will be. see case above. } else half_switch++; - // 64T0 + // 64T0 } else if (ABS(lastClockDuration - _64T0) < TOLERANCE) { // this means, bit here is 1 bits[bitPos] = 1; bitPos++; - - // Error + + // Error } else { - // some Error. maybe check tolerances. + // some Error. maybe check tolerances. // likeley to happen in the first block. - + // In an Ideal world, this can be enabled. However, if only bad antenna field, this print will flood the output // and one might miss some "good" frames. //Dbprintf(_RED_("ERROR in demodulation") " Length last clock: %d - check threshold/tolerance/signal. Toss block", lastClockDuration*DECIMATION); @@ -168,7 +168,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { if (block_done == 1) { // Dbprintf(_YELLOW_("Block Done") " bitPos: %d, sample: %d", bitPos, sample); - + // check if it is a complete block. If bitpos <128, it means that we did not receive // a complete block. E.g. at the first start of a transmission. // only save if a complete block is being received. @@ -187,14 +187,14 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } num_blocks++; } - // now start over for the next block / first complete block. + // now start over for the next block / first complete block. bitPos = 0; block_done = 0; half_switch = 0; } - }else { - // Dbprintf("%d, %d", sample, dest[sample]); + } else { + // Dbprintf("%d, %d", sample, dest[sample]); } // one block only holds 16byte (=128 bit) and then comes the PMC. so if more bit are found than 129, there must be an issue and PMC has not been identfied... @@ -204,8 +204,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { bitPos = 0; } - } - + } + memcpy(outBlocks, blocks, 16 * num_blocks); return num_blocks; } @@ -251,10 +251,10 @@ bool IsBlock1PCF7931(const uint8_t *block) { } void ReadPCF7931(bool ledcontrol) { - + uint8_t maxBlocks = 8; // readable blocks int found_blocks = 0; // successfully read blocks - + // TODO: Why 17 byte len? 16 should be good. uint8_t memory_blocks[maxBlocks][17]; // PCF content uint8_t single_blocks[maxBlocks][17]; // PFC blocks with unknown position @@ -263,7 +263,7 @@ void ReadPCF7931(bool ledcontrol) { int single_blocks_cnt = 0; size_t n; // transmitted blocks - + //uint8_t found_0_1 = 0; // flag: blocks 0 and 1 were found int errors = 0; // error counter int tries = 0; // tries counter @@ -300,7 +300,7 @@ void ReadPCF7931(bool ledcontrol) { goto end; } - // This part was not working properly. + // This part was not working properly. // So currently the blocks are not being sorted, but at least printed. // // our logic breaks if we don't get at least two blocks @@ -403,28 +403,28 @@ void ReadPCF7931(bool ledcontrol) { end: -/* - Dbprintf("-----------------------------------------"); - Dbprintf("Memory content:"); - Dbprintf("-----------------------------------------"); - for (i = 0; i < maxBlocks; ++i) { - if (memory_blocks[i][ALLOC]) - print_result("Block", memory_blocks[i], 16); - else - Dbprintf("", i); - } - Dbprintf("-----------------------------------------"); + /* + Dbprintf("-----------------------------------------"); + Dbprintf("Memory content:"); + Dbprintf("-----------------------------------------"); + for (i = 0; i < maxBlocks; ++i) { + if (memory_blocks[i][ALLOC]) + print_result("Block", memory_blocks[i], 16); + else + Dbprintf("", i); + } + Dbprintf("-----------------------------------------"); - if (found_blocks < maxBlocks) { - Dbprintf("-----------------------------------------"); - Dbprintf("Blocks with unknown position:"); - Dbprintf("-----------------------------------------"); - for (i = 0; i < single_blocks_cnt; ++i) - print_result("Block", single_blocks[i], 16); + if (found_blocks < maxBlocks) { + Dbprintf("-----------------------------------------"); + Dbprintf("Blocks with unknown position:"); + Dbprintf("-----------------------------------------"); + for (i = 0; i < single_blocks_cnt; ++i) + print_result("Block", single_blocks[i], 16); - Dbprintf("-----------------------------------------"); - } -*/ + Dbprintf("-----------------------------------------"); + } + */ reply_mix(CMD_ACK, 0, 0, 0, 0, 0); } @@ -434,7 +434,7 @@ static void RealWritePCF7931( uint16_t init_delay, int8_t offsetPulseWidth, int8_t offsetPulsePosition, uint8_t address, uint8_t byte, uint8_t data, - bool ledcontrol){ + bool ledcontrol) { uint32_t tab[1024] = {0}; // data times frame uint32_t u = 0; @@ -512,10 +512,10 @@ static void RealWritePCF7931( * @param data : data to write */ void WritePCF7931( - uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, - uint16_t init_delay, - int8_t offsetPulseWidth, int8_t offsetPulsePosition, - uint8_t address, uint8_t byte, uint8_t data, + uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, + uint16_t init_delay, + int8_t offsetPulseWidth, int8_t offsetPulsePosition, + uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol) { if (g_dbglevel >= DBG_INFO) { @@ -550,7 +550,7 @@ void SendCmdPCF7931(uint32_t *tab, bool ledcontrol) { FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_PASSTHRU); if (ledcontrol) LED_A_ON(); - + // rescale the values to match the time of the timer below. for (u = 0; u < 500; ++u) { tab[u] = (tab[u] * 3) / 2; @@ -651,7 +651,7 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offset tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; tab[u + 2] = 88 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; - + } else { //add a bit 0 if (u == 0) tab[u] = 98 * T0_PCF + offsetPulsePosition; @@ -660,7 +660,7 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offset tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; tab[u + 2] = 24 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; - + } return true; } diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index 314fb7e3c..e412a7a66 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -19,9 +19,9 @@ #include "common.h" -typedef enum{ +typedef enum { FALLING, - RISING + RISING } EdgeType; size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol); diff --git a/client/luascripts/Paxton_clone.lua b/client/luascripts/Paxton_clone.lua index dcf99e5bf..523eb07c3 100644 --- a/client/luascripts/Paxton_clone.lua +++ b/client/luascripts/Paxton_clone.lua @@ -1,4 +1,3 @@ - local getopt = require('getopt') local utils = require('utils') local ac = require('ansicolors') @@ -8,11 +7,14 @@ local dir = os.getenv('HOME') .. '/.proxmark3/logs/' local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) local log_file_path = dir .. "Paxton_log.txt" local nam = "" +local pm3 = require('pm3') +p = pm3.pm3() local command = core.console +command('clear') author = ' Author: jareckib - 30.01.2025' tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102' -version = ' version v1.18' +version = ' version v1.19' desc = [[ The script automates the copying of Paxton fobs read - write. It also allows manual input of data for blocks 4-7. @@ -228,13 +230,10 @@ end local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) while true do - print(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2" .. ac.reset) - print(dash) - io.write(" Your choice "..ac.cyan.."(1/2): "..ac.reset) + io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset) local choice = io.read() if choice == "1" then - print(dash) - print(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..") + io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..") io.read() print(dash) command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846") @@ -242,20 +241,18 @@ local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846") command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846") elseif choice == "2" then - print(dash) - print(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..") + io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..") io.read() - print(dash) - command("lf em 410x clone --id " .. padded_hex_id) + p:console("lf em 410x clone --id " .. padded_hex_id) + print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset) else print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset) goto ask_again end while true do print(dash) - io.write(" Make next RFID Fob"..ac.cyan.." (y/n)"..ac.reset.." > "..ac.yellow) + io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset) local another = io.read() - io.write(ac.reset..'') if another:lower() == "n" then if was_option_3 then print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist") @@ -303,7 +300,6 @@ local function main(args) if o == 'h' then return help() end end command('clear') - print() print(dash) print(ac.green .. ' Select option: ' .. ac.reset) print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy') @@ -324,12 +320,11 @@ local function main(args) local show_place_message = true while true do if show_place_message then - print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') - print(dash) + io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') end io.read() - command('lf hitag read --ht2 -k BDF5E846') - command('clear') + print(dash) + p:console('lf hitag read --ht2 -k BDF5E846') if not logfile then error(" No files in this directory") end @@ -343,12 +338,11 @@ local function main(args) end end if empty_block then - print(dash) - print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') - print(dash) + io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') show_place_message = false else - print(dash) + print(' Readed blocks:') + print() for i = 4, 7 do if blocks[i] then print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset)) @@ -364,7 +358,6 @@ local function main(args) print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) end - print(dash) print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) print(dash) handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) diff --git a/client/luascripts/lf_t55xx_fix.lua b/client/luascripts/lf_t55xx_fix.lua index 9a90b3f49..52af93a76 100644 --- a/client/luascripts/lf_t55xx_fix.lua +++ b/client/luascripts/lf_t55xx_fix.lua @@ -11,7 +11,7 @@ local command = core.console command('clear') author = ' Author: jareckib - 15.02.2025' -version = ' version v1.01' +version = ' version v1.02' desc = [[ This simple script first checks if a password has been set for the T5577. It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password @@ -102,7 +102,7 @@ local function reanimate_t5577(password) p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000') p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000') p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000') - p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') + p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000') reset_log_file() end @@ -111,20 +111,23 @@ local function main(args) if o == 'h' then return help() end end p:console('clear') - print(' I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset) - print(dash) + print(dash) + print('I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset) + io.write("Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..") + io.read() + print(dash) print("::: "..ac.cyan.."Hold on, I'm searching for a password in the dictionary"..ac.reset.." :::") print(dash) p:console('lf t55 chk') - timer(5) local log_content = read_log_file(logfile) local password = log_content and extract_password(log_content) or nil reanimate_t5577(password) - p:console('lf t55 detect') + p:console('lf t55 detect') + p:console('lf t55 read -b 0') timer(5) local success = false for line in p.grabbed_output:gmatch("[^\r\n]+") do - if line:find("000880E0") then + if line:find("00 | 000880E0 |") then success = true break end diff --git a/client/luascripts/lf_t55xx_multiwriter.lua b/client/luascripts/lf_t55xx_multiwriter.lua index cfaaeb112..669b51791 100644 --- a/client/luascripts/lf_t55xx_multiwriter.lua +++ b/client/luascripts/lf_t55xx_multiwriter.lua @@ -9,14 +9,14 @@ local command = core.console command('clear') author = ' Author: jareckib - 12.03.2025' version = ' version v1.03' -desc = [[ +desc = [[ This simple script stores 1, 2 or 3 different EM4102 on a single T5577. There is an option to enter the number engraved on the fob in decimal form. - The script can therefore be useful if the original EM4102 doesn't work but - has an engraved ID number. By entering such an ID as a single EM4102, we + The script can therefore be useful if the original EM4102 doesn't work but + has an engraved ID number. By entering such an ID as a single EM4102, we can create a working copy of our damaged fob. A tag T5577 created in this way works with the following USB readers: - + - ACM08Y - ACM26C - Sycreader R60D @@ -121,7 +121,7 @@ local function get_uid_from_user() while true do print(dash) io.write(ac.cyan .. '(1)' .. ac.reset .. ' Manual entry UID |' .. ac.cyan .. ' (2)' .. ac.reset .. ' Read via Proxmark3 ') - + local choice repeat choice = io.read() @@ -159,7 +159,7 @@ local function get_uid_from_user() io.read() while true do - reset_log_file() + reset_log_file() command('lf em 410x read') local log_content = read_log_file(logfile) local uid = extract_uid(log_content) @@ -178,7 +178,7 @@ end local function main(args) for o, a in getopt.getopt(args, 'h') do if o == 'h' then return help() end - end + end local blocks = {} local uid_count = 0 @@ -224,4 +224,4 @@ local function main(args) print(ac.green .. "Successfully written " .. uid_count .. " EM4102 UID(s) to T5577" .. ac.reset) end -main(args) \ No newline at end of file +main(args) diff --git a/client/luascripts/paxton_clone.lua b/client/luascripts/paxton_clone.lua new file mode 100644 index 000000000..1ece7303e --- /dev/null +++ b/client/luascripts/paxton_clone.lua @@ -0,0 +1,462 @@ +local getopt = require('getopt') +local utils = require('utils') +local ac = require('ansicolors') +local os = require('os') +local dash = string.rep('--', 32) +local dir = os.getenv('HOME') .. '/.proxmark3/logs/' +local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) +local log_file_path = dir .. "Paxton_log.txt" +local nam = "" +local pm3 = require('pm3') +p = pm3.pm3() +local command = core.console +command('clear') + +author = ' Author: jareckib - 30.01.2025' +tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102' +version = ' version v1.18' +desc = [[ + The script automates the copying of Paxton fobs read - write. + It also allows manual input of data for blocks 4-7. + The third option is reading data stored in the log file and create new fob. + Additionally, the script calculates the ID for downgrading Paxton to EM4102. + + ]] +usage = [[ + script run paxton_clone +]] +arguments = [[ + script run paxton_clone -h : this help +]] + +local debug = true + +local function dbg(args) + if not DEBUG then return end + if type(args) == 'table' then + local i = 1 + while args[i] do + dbg(args[i]) + i = i+1 + end + else + print('###', args) + end +end + +local function help() + print() + print(author) + print(tutorial) + print(version) + print(desc) + print(ac.cyan..' Usage'..ac.reset) + print(usage) + print(ac.cyan..' Arguments'..ac.reset) + print(arguments) +end + +local function read_log_file(logfile) + local file = io.open(logfile, "r") + if not file then + error(" Could not open the file") + end + local content = file:read("*all") + file:close() + return content +end + +local function parse_blocks(result) + local blocks = {} + for line in result:gmatch("[^\r\n]+") do + local block_num, block_data = line:match("%[%=%]%s+%d/0x0([4-7])%s+%|%s+([0-9A-F ]+)") + if block_num and block_data then + block_num = tonumber(block_num) + block_data = block_data:gsub("%s+", "") + blocks[block_num] = block_data + end + end + return blocks +end + +local function hex_to_bin(hex_string) + local bin_string = "" + local hex_to_bin_map = { + ['0'] = "0000", ['1'] = "0001", ['2'] = "0010", ['3'] = "0011", + ['4'] = "0100", ['5'] = "0101", ['6'] = "0110", ['7'] = "0111", + ['8'] = "1000", ['9'] = "1001", ['A'] = "1010", ['B'] = "1011", + ['C'] = "1100", ['D'] = "1101", ['E'] = "1110", ['F'] = "1111" + } + for i = 1, #hex_string do + bin_string = bin_string .. hex_to_bin_map[hex_string:sub(i, i)] + end + return bin_string +end + +local function remove_last_two_bits(binary_str) + return binary_str:sub(1, #binary_str - 2) +end + +local function split_into_5bit_chunks(binary_str) + local chunks = {} + for i = 1, #binary_str, 5 do + table.insert(chunks, binary_str:sub(i, i + 4)) + end + return chunks +end + +local function remove_parity_bit(chunks) + local no_parity_chunks = {} + for _, chunk in ipairs(chunks) do + if #chunk == 5 then + table.insert(no_parity_chunks, chunk:sub(2)) + end + end + return no_parity_chunks +end + +local function convert_to_hex(chunks) + local hex_values = {} + for _, chunk in ipairs(chunks) do + if #chunk > 0 then + table.insert(hex_values, string.format("%X", tonumber(chunk, 2))) + end + end + return hex_values +end + +local function convert_to_decimal(chunks) + local decimal_values = {} + for _, chunk in ipairs(chunks) do + table.insert(decimal_values, tonumber(chunk, 2)) + end + return decimal_values +end + +local function find_until_before_f(hex_values) + local result = {} + for _, value in ipairs(hex_values) do + if value == 'F' then + break + end + table.insert(result, value) + end + return result +end + +local function process_block(block) + local binary_str = hex_to_bin(block) + binary_str = remove_last_two_bits(binary_str) + local chunks = split_into_5bit_chunks(binary_str) + local no_parity_chunks = remove_parity_bit(chunks) + return no_parity_chunks +end + +local function calculate_id_net(blocks) + local all_hex_values = {} + for _, block in ipairs(blocks) do + local hex_values = convert_to_hex(process_block(block)) + for _, hex in ipairs(hex_values) do + table.insert(all_hex_values, hex) + end + end + local selected_hex_values = find_until_before_f(all_hex_values) + if #selected_hex_values == 0 then + error(ac.red..' Error: '..ac.reset..'No valid data found in blocks 4 and 5') + end + local combined_hex = table.concat(selected_hex_values) + if not combined_hex:match("^%x+$") then + error(ac.red..' Error: '..ac.reset..'Invalid data in blocks 4 and 5') + end + local decimal_id = tonumber(combined_hex) + local stripped_hex_id = string.format("%X", decimal_id) + local padded_hex_id = string.format("%010X", decimal_id) + return decimal_id, padded_hex_id +end + +local function calculate_id_switch(blocks) + local all_decimal_values = {} + for _, block in ipairs(blocks) do + local decimal_values = convert_to_decimal(process_block(block)) + for _, dec in ipairs(decimal_values) do + table.insert(all_decimal_values, dec) + end + end + if #all_decimal_values < 15 then + error(ac.red..' Error:'..ac.reset..' Not enough data after processing blocks 4, 5, 6, and 7') + end + local id_positions = {9, 11, 13, 15, 2, 4, 6, 8} + local id_numbers = {} + for _, pos in ipairs(id_positions) do + table.insert(id_numbers, all_decimal_values[pos]) + end + local decimal_id = tonumber(table.concat(id_numbers)) + local padded_hex_id = string.format("%010X", decimal_id) + return decimal_id, padded_hex_id +end + +local function name_exists_in_log(name) + local file = io.open(log_file_path, "r") + if not file then + return false + end + local pattern = "^Name:%s*" .. name .. "%s*$" + for line in file:lines() do + if line:match(pattern) then + file:close() + return true + end + end + file:close() + return false +end + +local function log_result(blocks, em410_id, name) + local log_file = io.open(log_file_path, "a") + if log_file then + log_file:write("Name: " .. name .. "\n") + log_file:write("Date: ", os.date("%Y-%m-%d %H:%M:%S"), "\n") + for i = 4, 7 do + log_file:write(string.format("Block %d: %s\n", i, blocks[i] or "nil")) + end + log_file:write(string.format('EM4102 ID: %s\n', em410_id or "nil")) + log_file:write('--------------------------\n') + log_file:close() + print(' Log saved as: pm3/.proxmark3/logs/' ..ac.yellow..' Paxton_log.txt'..ac.reset) + else + print(" Failed to open log file for writing.") + end +end + +local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) + while true do + io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset) + local choice = io.read() + if choice == "1" then + io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..") + io.read() + print(dash) + command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846") + command("lf hitag wrbl --ht2 -p 5 -d " .. blocks[5] .. " -k BDF5E846") + command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846") + command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846") + elseif choice == "2" then + io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..") + io.read() + p:console("lf em 410x clone --id " .. padded_hex_id) + print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset) + else + print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset) + goto ask_again + end + while true do + print(dash) + io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset) + local another = io.read() + if another:lower() == "n" then + if was_option_3 then + print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist") + return + end + print() + print(ac.green .. " Saving Paxton_log file..." .. ac.reset) + while true do + io.write(" Enter a name for database (cannot be empty/duplicate): "..ac.yellow) + name = io.read() + io.write(ac.reset..'') + if name == nil or name:match("^%s*$") then + print(ac.red .. ' ERROR:'..ac.reset..' Name cannot be empty.') + else + if name_exists_in_log(name) then + print(ac.yellow .. ' Name exists!!! '..ac.reset.. 'Please choose a different name.') + else + break + end + end + end + log_result(blocks, padded_hex_id, name) + print(ac.green .. " Log saved successfully!" .. ac.reset) + local file = io.open(logfile, "w+") + file:write("") + file:close() + return + elseif another:lower() == "y" then + goto ask_again + else + print(ac.yellow.." Invalid response."..ac.reset.." Please enter"..ac.cyan.." y"..ac.reset.." or"..ac.cyan.." n"..ac.reset) + end + end + ::ask_again:: + end +end + +local function is_valid_hex(input) + return #input == 8 and input:match("^[0-9A-Fa-f]+$") +end + +local function main(args) + while true do + for o, a in getopt.getopt(args, 'h') do + if o == 'h' then return help() end + end + command('clear') + print(dash) + print(ac.green .. ' Select option: ' .. ac.reset) + print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy') + print(ac.cyan .. ' 2' .. ac.reset .. ' - Manually input data for Paxton blocks 4-7') + print(ac.cyan .. " 3" .. ac.reset .. " - Search in Paxton_log by name and use the data") + print(dash) + while true do + io.write(' Your choice '..ac.cyan..'(1/2/3): ' .. ac.reset) + input_option = io.read() + if input_option == "1" or input_option == "2" or input_option == "3" then + break + else + print(ac.yellow .. ' Invalid choice.' .. ac.reset .. ' Please enter ' .. ac.cyan .. '1' .. ac.reset .. ' or ' .. ac.cyan .. '2' .. ac.reset..' or'..ac.cyan..' 3'..ac.reset) + end + end + local was_option_3 = false + if input_option == "1" then + local show_place_message = true + while true do + if show_place_message then + io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') + end + io.read() + print(dash) + p:console('lf hitag read --ht2 -k BDF5E846') + if not logfile then + error(" No files in this directory") + end + local result = read_log_file(logfile) + local blocks = parse_blocks(result) + local empty_block = false + for i = 4, 7 do + if not blocks[i] then + empty_block = true + break + end + end + if empty_block then + io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') + show_place_message = false + else + print(' Readed blocks:') + print() + for i = 4, 7 do + if blocks[i] then + print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset)) + end + end + local decimal_id, padded_hex_id + if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then + print(dash) + print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset) + decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]}) + else + print(dash) + print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) + decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) + end + print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) + print(dash) + handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) + break + end + end + elseif input_option == "2" then + local blocks = {} + for i = 4, 7 do + while true do + io.write(ac.reset..' Enter data for block ' .. i .. ': ' .. ac.yellow) + local input = io.read() + input = input:upper() + if is_valid_hex(input) then + blocks[i] = input + break + else + print(ac.yellow .. ' Invalid input.' .. ac.reset .. ' Each block must be 4 bytes (8 hex characters).') + end + end + end + local decimal_id, padded_hex_id + if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then + print(ac.reset.. dash) + print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset) + decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]}) + else + print(ac.reset.. dash) + print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) + decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) + end + print(dash) + print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) + print(dash) + if not padded_hex_id then + print(ac.red..' ERROR: '..ac.reset.. 'Invalid block data provided') + return + end + handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) + break + elseif input_option == "3" then + was_option_3 = true + local retries = 3 + while retries > 0 do + io.write(' Enter the name to search ('..retries..' attempts) : '..ac.yellow) + local user_input = io.read() + io.write(ac.reset..'') + if user_input == nil or user_input:match("^%s*$") then + print(ac.yellow..' Error: '..ac.reset.. 'Empty name !!!') + end + local name_clean = "^Name:%s*" .. user_input:gsub("%s", "%%s") .. "%s*$" + local file = io.open(log_file_path, "r") + if not file then + print(ac.red .. ' Error:'..ac.reset.. 'Could not open log file.') + return + end + local lines = {} + for line in file:lines() do + table.insert(lines, line) + end + file:close() + local found = false + for i = 1, #lines do + if lines[i]:match(name_clean) then + nam = user_input + local blocks = { + [4] = lines[i + 2]:match("Block 4: (.+)"), + [5] = lines[i + 3]:match("Block 5: (.+)"), + [6] = lines[i + 4]:match("Block 6: (.+)"), + [7] = lines[i + 5]:match("Block 7: (.+)") + } + local em4102_id = lines[i + 6]:match("EM4102 ID: (.+)") + print(dash) + print(' I found the data under the name: '..ac.yellow ..nam.. ac.reset) + for j = 4, 7 do + print(string.format(" Block %d: %s%s%s", j, ac.yellow, blocks[j] or "N/A", ac.reset)) + end + print(" EM4102 ID: " .. ac.green .. (em4102_id or "N/A") .. ac.reset) + print(dash) + local decimal_id, padded_hex_id = em4102_id, em4102_id + handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3, nam) + found = true + break + end + end + if not found then + retries = retries - 1 + else + break + end + end + if retries == 0 then + print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset) + end + end + print(dash) + print(' Exiting script Lua...') + return + end +end + +main(args) diff --git a/client/pyscripts/hf_mfu_uscuid.py b/client/pyscripts/hf_mfu_uscuid.py index a477c1b16..417694a33 100644 --- a/client/pyscripts/hf_mfu_uscuid.py +++ b/client/pyscripts/hf_mfu_uscuid.py @@ -77,14 +77,14 @@ def parse_config(config: str): cfg_auth_type = config[8:10] cfg_cuid = config[12:14] cfg_memory_config = config[14:16] - + log_magic_wup = "Magic wakeup " + ("en" if cfg_magic_wup != "8500" else "dis") + "abled" + (" with config access" if cfg_magic_wup == "7AFF" else "") log_wup_style = "Magic wakeup style " + ("Gen1a 40(7)/43" if cfg_wup_style == "00" else ("GDM 20(7)/23" if cfg_wup_style == "85" else "unknown")) log_regular_available = "Config " + ("" if cfg_regular_available == "A0" else "un") + "available in regular mode" log_auth_type = "Auth type " + ("1B - PWD" if cfg_auth_type == "00" else "1A - 3DES") log_cuid = "CUID " + ("dis" if cfg_cuid == "A0" else "en") + "abled" log_memory_config = "Maximum memory configuration: " + (MEMORY_CONFIG[cfg_memory_config] if cfg_memory_config in MEMORY_CONFIG.keys() else "unknown") - + print(SUCCESS + "^^^^............................ " + log_magic_wup) print(SUCCESS + "....^^.......................... " + log_wup_style) print(SUCCESS + "......^^........................ " + log_regular_available) @@ -93,7 +93,7 @@ def parse_config(config: str): print(SUCCESS + "............^^.................. " + log_cuid) print(SUCCESS + "..............^^................ " + log_memory_config) print(SUCCESS + "................^^^^^^^^^^^^^^^^ version info") - + def try_auth_magic(enforced = False): if enforced and not (gen1a | alt): print(ERROR + "Magic wakeup required. Please select one.") @@ -158,7 +158,7 @@ elif write_backdoor != None: if len(data) % 8 != 0: print(ERROR + "Data must be a multiple of 4 bytes.") exit() - + try_auth_magic(True) for i in range(len(data) // 8): p.console("hf 14a raw -" + ("k" if i != (len(data) // 8 - 1) else "") + f"c A2{(write_backdoor_num + i):02x}{data[8*i:8*i+8]}", False, False) @@ -171,13 +171,13 @@ elif uid != None: p.console(f"hf 14a raw -kc" + ("s" if not (gen1a or alt) else "") + " 3002") block_2 = p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")[:8] uid_bytes = [int(uid[2*x:2*x+2], 16) for x in range(7)] - + bcc_0 = 0x88 ^ uid_bytes[0] ^ uid_bytes[1] ^ uid_bytes[2] new_block_0 = "" for i in range(3): new_block_0 += f"{uid_bytes[i]:02x}" new_block_0 += f"{bcc_0:02x}" - + bcc_1 = uid_bytes[3] ^ uid_bytes[4] ^ uid_bytes[5] ^ uid_bytes[6] new_block_1 = uid[6:] new_block_2 = f"{bcc_1:02x}" + block_2[2:] diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index 145392eb7..7f296336c 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -688,11 +688,11 @@ static int CmdEM410xClone(const char *Cmd) { uint8_t r_parity = 0; uint8_t nibble = id >> i & 0xF; - databits = concatbits(data, databits, &nibble, 4, 4); + databits = concatbits(data, databits, &nibble, 4, 4, false); for (size_t j = 0; j < 4; j++) { r_parity ^= nibble >> j & 1; } - databits = concatbits(data, databits, &r_parity, 7, 1); + databits = concatbits(data, databits, &r_parity, 7, 1, false); c_parity ^= nibble; } data[7] |= c_parity << 1; diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index cffac044d..bfd7529fe 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/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 6230cbf35..ec9e7361a 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -832,7 +832,7 @@ static bool ht2_get_uid(uint32_t *uid) { } if (resp.status != PM3_SUCCESS) { - PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting UID"); + PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag 2 UID"); return false; } diff --git a/client/src/ui.c b/client/src/ui.c index 68cc71a43..cc6a411ce 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -186,6 +186,7 @@ static void fill_grabber(const char *string) { g_grabbed_output.ptr = tmp; g_grabbed_output.size += MAX_PRINT_BUFFER; } + int len = snprintf(g_grabbed_output.ptr + g_grabbed_output.idx, MAX_PRINT_BUFFER, "%s", string); if (len < 0 || len > MAX_PRINT_BUFFER) { // We leave current g_grabbed_output_len untouched @@ -196,24 +197,34 @@ static void fill_grabber(const char *string) { } void PrintAndLogOptions(const char *str[][2], size_t size, size_t space) { + char buff[2000] = "Options:\n"; char format[2000] = ""; size_t counts[2] = {0, 0}; - for (size_t i = 0; i < size; i++) - for (size_t j = 0 ; j < 2 ; j++) + + for (size_t i = 0; i < size; i++) { + for (size_t j = 0 ; j < 2 ; j++) { if (counts[j] < strlen(str[i][j])) { counts[j] = strlen(str[i][j]); } + } + } + for (size_t i = 0; i < size; i++) { + for (size_t j = 0; j < 2; j++) { - if (j == 0) + if (j == 0) { snprintf(format, sizeof(format), "%%%zus%%%zus", space, counts[j]); - else + } else { snprintf(format, sizeof(format), "%%%zus%%-%zus", space, counts[j]); + } + snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), format, " ", str[i][j]); } - if (i < size - 1) + + if (i < size - 1) { strncat(buff, "\n", sizeof(buff) - strlen(buff) - 1); + } } PrintAndLogEx(NORMAL, "%s", buff); } @@ -223,12 +234,14 @@ static uint8_t PrintAndLogEx_spinidx = 0; void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { // skip debug messages if client debugging is turned off i.e. 'DATA SETDEBUG -0' - if (g_debugMode == 0 && level == DEBUG) + if (g_debugMode == 0 && level == DEBUG) { return; + } // skip HINT messages if client has hints turned off i.e. 'HINT 0' - if (g_session.show_hints == false && level == HINT) + if (g_session.show_hints == false && level == HINT) { return; + } char prefix[40] = {0}; char buffer[MAX_PRINT_BUFFER] = {0}; @@ -242,17 +255,19 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { }; switch (level) { case ERR: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _RED_("!!") "] :rotating_light: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _RED_("!!") "] ", sizeof(prefix) - 1); + } stream = stderr; break; case FAILED: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _RED_("-") "] :no_entry: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _RED_("-") "] ", sizeof(prefix) - 1); + } break; case DEBUG: strncpy(prefix, "[" _BLUE_("#") "] ", sizeof(prefix) - 1); @@ -264,10 +279,11 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { strncpy(prefix, "[" _GREEN_("+") "] ", sizeof(prefix) - 1); break; case WARNING: - if (g_session.emoji_mode == EMO_EMOJI) + if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, "[" _CYAN_("!") "] :warning: ", sizeof(prefix) - 1); - else + } else { strncpy(prefix, "[" _CYAN_("!") "] ", sizeof(prefix) - 1); + } break; case INFO: strncpy(prefix, "[" _YELLOW_("=") "] ", sizeof(prefix) - 1); @@ -276,13 +292,15 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { if (g_session.emoji_mode == EMO_EMOJI) { strncpy(prefix, spinner_emoji[PrintAndLogEx_spinidx], sizeof(prefix) - 1); PrintAndLogEx_spinidx++; - if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji)) + if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji)) { PrintAndLogEx_spinidx = 0; + } } else { strncpy(prefix, spinner[PrintAndLogEx_spinidx], sizeof(prefix) - 1); PrintAndLogEx_spinidx++; - if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner)) + if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner)) { PrintAndLogEx_spinidx = 0; + } } break; case NORMAL: @@ -306,8 +324,9 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { const char delim[2] = "\n"; // line starts with newline - if (buffer[0] == '\n') + if (buffer[0] == '\n') { fPrintAndLog(stream, ""); + } token = strtok_r(buffer, delim, &tmp_ptr); @@ -315,16 +334,21 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) { size_t size = strlen(buffer2); - if (strlen(token)) + if (strlen(token)) { snprintf(buffer2 + size, sizeof(buffer2) - size, "%s%s\n", prefix, token); - else + } else { snprintf(buffer2 + size, sizeof(buffer2) - size, "\n"); + } token = strtok_r(NULL, delim, &tmp_ptr); } + fPrintAndLog(stream, "%s", buffer2); + } else { + snprintf(buffer2, sizeof(buffer2), "%s%s", prefix, buffer); + if (level == INPLACE) { // ignore INPLACE if rest of output is grabbed if (!(g_printAndLog & PRINTANDLOG_GRAB)) { @@ -354,6 +378,7 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { if (logging && g_session.incognito) { logging = 0; } + if ((g_printAndLog & PRINTANDLOG_LOG) && logging && !logfile) { char *my_logfile_path = NULL; char filename[40]; @@ -361,11 +386,15 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { time_t now = time(NULL); timenow = gmtime(&now); strftime(filename, sizeof(filename), PROXLOG, timenow); + if (searchHomeFilePath(&my_logfile_path, LOGS_SUBDIR, filename, true) != PM3_SUCCESS) { + printf(_YELLOW_("[-]") " Logging disabled!\n"); my_logfile_path = NULL; logging = 0; + } else { + logfile = fopen(my_logfile_path, "a"); if (logfile == NULL) { printf(_YELLOW_("[-]") " Can't open logfile %s, logging disabled!\n", my_logfile_path); @@ -411,13 +440,16 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { linefeed = false; buffer[strlen(buffer) - 1] = 0; } + bool filter_ansi = !g_session.supports_colors; memcpy_filter_ansi(buffer2, buffer, sizeof(buffer), filter_ansi); - if (g_printAndLog & PRINTANDLOG_PRINT) { + + if ((g_printAndLog & PRINTANDLOG_PRINT) == PRINTANDLOG_PRINT) { memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), g_session.emoji_mode); fprintf(stream, "%s", buffer3); - if (linefeed) + if (linefeed) { fprintf(stream, "\n"); + } } #ifdef RL_STATE_READCMD @@ -433,33 +465,44 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) { if (((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) || (g_printAndLog & PRINTANDLOG_GRAB)) { + memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), EMO_ALTTEXT); + if (filter_ansi == false) { memcpy_filter_ansi(buffer, buffer3, sizeof(buffer3), true); } } + if ((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) { + if (filter_ansi) { fprintf(logfile, "%s", buffer3); } else { fprintf(logfile, "%s", buffer); } - if (linefeed) + + if (linefeed) { fprintf(logfile, "\n"); + } fflush(logfile); } + if (g_printAndLog & PRINTANDLOG_GRAB) { + if (filter_ansi) { fill_grabber(buffer3); } else { fill_grabber(buffer); } - if (linefeed) + + if (linefeed) { fill_grabber("\n"); + } } - if (flushAfterWrite) + if (flushAfterWrite) { fflush(stdout); + } //release lock pthread_mutex_unlock(&g_print_lock); @@ -478,9 +521,10 @@ void memcpy_filter_rlmarkers(void *dest, const void *src, size_t n) { uint8_t *rsrc = (uint8_t *)src; uint16_t si = 0; for (size_t i = 0; i < n; i++) { - if ((rsrc[i] == '\001') || (rsrc[i] == '\002')) + if ((rsrc[i] == '\001') || (rsrc[i] == '\002')) { // skip readline special markers continue; + } rdest[si++] = rsrc[i]; } } diff --git a/common/commonutil.c b/common/commonutil.c index d4eed608e..35a583988 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -555,9 +555,13 @@ void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len) { } // TODO: Boost performance by copying in chunks of 1, 2, or 4 bytes when feasible. -size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits) { +/** + * @brief Concatenate bits from src to dest, bitstream is stored MSB first + * which means that the dest_offset=0 is the MSB of the dest[0] + * + */ +size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb) { int i, end, step; - // overlap if ((src - dest) * 8 + src_offset - dest_offset > 0) { i = 0; @@ -571,8 +575,8 @@ size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_of for (; i != end; i += step) { // equiv of dest_bits[dest_offset + i] = src_bits[src_offset + i] - CLEAR_BIT(dest, dest_offset + i); - if (TEST_BIT(src, src_offset + i)) SET_BIT(dest, dest_offset + i); + CLEAR_BIT_MSB(dest, dest_offset + i); + if (src_lsb ? TEST_BIT_LSB(src, src_offset + i) : TEST_BIT_MSB(src, src_offset + i)) SET_BIT_MSB(dest, dest_offset + i); } return dest_offset + nbits; diff --git a/common/commonutil.h b/common/commonutil.h index 2ab4fbd79..2360f2a57 100644 --- a/common/commonutil.h +++ b/common/commonutil.h @@ -150,7 +150,7 @@ bool hexstr_to_byte_array(const char *hexstr, uint8_t *d, size_t *n); void reverse_arraybytes(uint8_t *arr, size_t len); void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len); -size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits); +size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb); int char2int(char c); int hexstr2ByteArr(const char *hexstr, unsigned char *array, size_t asize); #endif diff --git a/doc/commands.json b/doc/commands.json index 04d547610..584caef94 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -5047,7 +5047,7 @@ "-v, --verbose verbose output", "-f, --file Specify a filename for dump file", "--emu from emulator memory", - "--start index of block to start writing (default 0)", + "--start index of block to start writing (def 0)", "--end index of block to end writing (default last block)" ], "usage": "hf mf gload [-hv] [--mini] [--1k] [--1k+] [--2k] [--4k] [-p ] [-f ] [--emu] [--start ] [--end ]" @@ -10992,8 +10992,8 @@ "-r, --reset Reset configuration to default values", "-p, --pwd Password, 7bytes, LSB-order", "-d, --delay Tag initialization delay (in us)", - "--lw offset, low pulses width (in us)", - "--lp offset, low pulses position (in us)" + "--lw offset, low pulses width (in us), optional!", + "--lp offset, low pulses position (in us), optional!" ], "usage": "lf pcf7931 config [-hr] [-p ] [-d ] [--lw ] [--lp ]" }, @@ -13232,6 +13232,6 @@ "metadata": { "commands_extracted": 760, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-12T15:46:33" + "extracted_on": "2025-03-18T06:54:58" } } 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/common.h b/include/common.h index b405b2ac7..086354750 100644 --- a/include/common.h +++ b/include/common.h @@ -202,10 +202,15 @@ extern bool g_tearoff_enabled; #endif // bit stream operations -#define TEST_BIT(data, i) (*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1 -#define SET_BIT(data, i) *((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8))) -#define CLEAR_BIT(data, i) *((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8))) -#define FLIP_BIT(data, i) *((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8))) +#define TEST_BIT_MSB(data, i) ((*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1) +#define SET_BIT_MSB(data, i) (*((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8)))) +#define CLEAR_BIT_MSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8)))) +#define FLIP_BIT_MSB(data, i) (*((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8)))) + +#define TEST_BIT_LSB(data, i) ((*((data) + ((i) / 8)) >> ((i) % 8)) & 1) +#define SET_BIT_LSB(data, i) (*((data) + ((i) / 8)) |= (1 << ((i) % 8))) +#define CLEAR_BIT_LSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << ((i) % 8))) +#define FLIP_BIT_LSB(data, i) (*((data) + ((i) / 8)) ^= (1 << ((i) % 8))) // time for decompressing and loading the image to the FPGA #define FPGA_LOAD_WAIT_TIME (1500) 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__ */