diff --git a/armsrc/Makefile b/armsrc/Makefile index 6ff1455de..9929b3ae3 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -72,7 +72,7 @@ else endif ifneq (,$(findstring WITH_HITAG,$(APP_CFLAGS))) - SRC_HITAG = hitag2_crypto.c hitag_common.c hitag2.c hitagS.c hitag2_crack.c + SRC_HITAG = hitag2_crypto.c hitag_common.c hitag2.c hitagS.c hitagu.c hitag2_crack.c APP_CFLAGS += -I../common/hitag2 else SRC_HITAG = diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e4e903a45..7907cc071 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -41,6 +41,7 @@ #include "hitag2.h" #include "hitag2_crack.h" #include "hitagS.h" +#include "hitagu.h" #include "em4x50.h" #include "em4x70.h" #include "iclass.h" @@ -1232,6 +1233,25 @@ static void PacketReceived(PacketCommandNG *packet) { memcpy(mem, payload->data, payload->len); break; } + + case CMD_LF_HITAGU_READ: { + lf_hitag_data_t *payload = (lf_hitag_data_t *)packet->data.asBytes; + htu_read(payload, true); + break; + } + case CMD_LF_HITAGU_WRITE: { + lf_hitag_data_t *payload = (lf_hitag_data_t *)packet->data.asBytes; + htu_write_page(payload, true); + break; + } + case CMD_LF_HITAGU_SIMULATE: { + htu_simulate((bool)packet->oldarg[0], packet->oldarg[1], packet->data.asBytes, true); + break; + } + case CMD_LF_HITAGU_UID: { + htu_read_uid(NULL, true, true); + break; + } #endif #ifdef WITH_EM4x50 diff --git a/armsrc/hitagu.c b/armsrc/hitagu.c new file mode 100644 index 000000000..0b1216edf --- /dev/null +++ b/armsrc/hitagu.c @@ -0,0 +1,897 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency HITAG µ (micro) functions + +#include "hitagu.h" +#include "hitag_common.h" + +#include "BigBuf.h" +#include "appmain.h" // tearoff_hook() +#include "cmd.h" +#include "commonutil.h" +#include "crc16.h" +#include "dbprint.h" +#include "fpgaloader.h" +#include "hitag2/hitag2_crypto.h" +#include "lfadc.h" +#include "protocols.h" +#include "proxmark3_arm.h" +#include "string.h" +#include "ticks.h" +#include "util.h" + +// Hitag µ specific definitions +#define HTU_SOF_BITS 4 // Start of frame bits is always 3 for Hitag µ (110) plus 1 bit error flag + +MOD M = MC4K; // Modulation type + +// Structure to hold the state of the Hitag µ tag +static struct hitagU_tag tag = { + .pstate = HT_READY, // Initial state is ready + .max_page = HITAGU_MAX_PAGE_STANDARD, // Default to standard version + .icr = 0, // Default ICR value +}; + +// Macros for managing authentication state +#define IS_AUTHENTICATED() (tag.pstate == HT_AUTHENTICATE) +#define SET_AUTHENTICATED() (tag.pstate = HT_AUTHENTICATE) +#define RESET_AUTHENTICATION() (tag.pstate = HT_READY) + +/* + * Update the maximum page number based on the tag's ICR (IC Revision) + */ +static void update_tag_max_page_by_icr(void) { + // Set max_page based on ICR value + switch (tag.icr) { + case HITAGU_ICR_STANDARD: + tag.max_page = HITAGU_MAX_PAGE_STANDARD; + DBG Dbprintf("Detected standard Hitag μ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_ADVANCED: + tag.max_page = HITAGU_MAX_PAGE_ADVANCED; + DBG Dbprintf("Detected Hitag μ advanced (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_ADVANCED_PLUS: + tag.max_page = HITAGU_MAX_PAGE_ADVANCED_PLUS; + DBG Dbprintf("Detected Hitag μ advanced+ (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + case HITAGU_ICR_8265: + tag.max_page = HITAGU_MAX_PAGE_8265; + DBG Dbprintf("Detected Hitag μ 8265 (ICR=0x%02X), max page: 0x%02X", tag.icr, tag.max_page); + break; + default: + // Unknown ICR, use standard size as fallback + tag.max_page = HITAGU_MAX_PAGE_STANDARD; + DBG Dbprintf("Unknown Hitag μ ICR: 0x%02X, defaulting to max page: 0x%02X", tag.icr, tag.max_page); + break; + } +} + +/* + * Update the maximum page number based on the tag's memory configuration + * This function checks both ICR and additional pattern-based detection + */ +static void update_tag_max_page(void) { + // First try to determine max_page from ICR + update_tag_max_page_by_icr(); + + // Additional tag type detection can be added here +} + +/* + * Handles all commands from a reader for Hitag µ + * Processes flags and commands, generates appropriate responses + */ +static void htu_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_t *tx, size_t *txlen) { + // Initialize response + *txlen = 0; + + if (rxlen < 5) { + return; // Command too short + } + + // Extract flags (5 bits) and command (6 bits if present) + uint8_t flags = (rx[0] >> 3) & 0x1F; + uint8_t command = 0; + + if (rxlen >= 11) { + // Extract 6-bit command if present + command = ((rx[0] & 0x07) << 3) | ((rx[1] >> 5) & 0x07); + } + + // Check flags + bool inv_flag = (flags & HITAGU_FLAG_INV); + bool crct_flag = (flags & HITAGU_FLAG_CRCT); + + // Handle based on flags and command + if (inv_flag) { + // Inventory mode - respond with UID (48 bits) + *txlen = concatbits(tx, *txlen, tag.uid, 0, HITAGU_UID_SIZE * 8, true); + } else if (command == HITAGU_CMD_LOGIN) { + // Login command + if (rxlen >= 43) { // 5+6+32 bits = 43 bits minimum + // Extract password - 32 bits after command + uint32_t password = 0; + for (int i = 0; i < 4; i++) { + int startBit = 11 + i * 8; // 5+6 bits of command + i*8 + uint8_t b = 0; + + for (int j = 0; j < 8; j++) { + int bitPos = startBit + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + b |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + password |= (b << (24 - i * 8)); + } + + // Check password + if (password == ((tag.password[0] << 24) | (tag.password[1] << 16) | (tag.password[2] << 8) | tag.password[3])) { + // Set authentication state + SET_AUTHENTICATED(); + + // Send success response + uint8_t resp_byte = 0x01; // Success code + *txlen = concatbits(tx, *txlen, &resp_byte, 0, 8, true); + } else { + // Authentication failed + RESET_AUTHENTICATION(); + + // Send failure response + uint8_t resp_byte = 0x00; // Failure code + *txlen = concatbits(tx, *txlen, &resp_byte, 0, 8, true); + } + } + } else if (command == HITAGU_CMD_SELECT) { + // Select command + if (rxlen >= 59) { // 5+6+48 bits = 59 bits minimum (48-bit UID) + // Extract UID to select - next 48 bits + uint8_t sel_uid[6] = {0}; + for (int i = 0; i < 6; i++) { + int startBit = 11 + i * 8; // 5+6 bits of command + i*8 + uint8_t b = 0; + + for (int j = 0; j < 8; j++) { + int bitPos = startBit + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + b |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + sel_uid[i] = b; + } + + // Check if UID matches + if (memcmp(sel_uid, tag.uid, 6) == 0) { + // Selected - send response with select data + uint8_t resp_data[4] = {0xCA, 0x24, 0x00, 0x00}; // Standard select response + *txlen = concatbits(tx, *txlen, resp_data, 0, 32, true); + } else { + // UID mismatch - no response + *txlen = 0; + } + } + } else if (command == HITAGU_CMD_READ_MULTIPLE_BLOCK) { + // Read command + if (rxlen >= 19) { // 5+6+8 bits = 19 bits minimum + // Extract page address - 8 bits after command + uint8_t page = 0; + for (int i = 0; i < 8; i++) { + int bitPos = 11 + i; // 5+6 bits of command + i + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + page |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + + // Extract number of blocks to read if ADR flag is set + uint8_t read_len = 1; // Default to 1 page + if ((flags & HITAGU_FLAG_ADR) && rxlen >= 27) { + for (int i = 0; i < 8; i++) { + int bitPos = 19 + i; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + if (pos < (rxlen + 7) / 8) { + read_len |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + } + } + + // Security check: does this page require authentication? + bool needs_auth = false; + // Check if page is password-protected (e.g., config or password page) + if (page == HITAGU_PASSWORD_PADR) { + needs_auth = true; + } + + // Check authentication for protected pages + if (needs_auth && !IS_AUTHENTICATED()) { + // Not authenticated, cannot read protected pages + DBG Dbprintf("Page %d requires authentication", page); + + // Mark as unauthorized access + *txlen = 0; // No response + } else { + // Map page address (some pages may be aliased) + uint8_t real_page = page; + if (page >= 64 && tag.max_page <= 64) { + real_page = page & 0x3F; // Pages above 64 map to 0-63 + } + + // Read requested number of pages + for (int i = 0; i < read_len && i < 16; i++) { // Limit to 16 pages max + uint8_t curr_page = (real_page + i) % tag.max_page; + + // Special pages + if (curr_page == HITAGU_CONFIG_PADR) { + // Config page + *txlen = concatbits(tx, *txlen, (uint8_t *)&tag.config, 0, 32, true); + } else if (curr_page == HITAGU_PASSWORD_PADR) { + // Password page - only return if authenticated + if (IS_AUTHENTICATED()) { + *txlen = concatbits(tx, *txlen, tag.password, 0, 32, true); + } else { + // Return zeros if not authenticated + uint8_t zeros[4] = {0}; + *txlen = concatbits(tx, *txlen, zeros, 0, 32, true); + } + } else { + // Regular page + *txlen = concatbits(tx, *txlen, tag.data.pages[curr_page], 0, 32, true); + } + } + } + } + } else if (command == HITAGU_CMD_WRITE_SINGLE_BLOCK) { + // Write command + if (rxlen >= 51) { // 5+6+8+32 bits = 51 bits minimum + // Check if authenticated + if (!IS_AUTHENTICATED()) { + DBG Dbprintf("WRITE failed: not authenticated"); + *txlen = 0; // No response + } else { + // Extract page address - 8 bits after command + uint8_t page = 0; + for (int i = 0; i < 8; i++) { + int bitPos = 11 + i; // 5+6 bits of command + i + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + page |= ((rx[pos] >> shift) & 0x01) << (7 - i); + } + + // Extract data - 32 bits after page address + uint8_t data[4] = {0}; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + int bitPos = 19 + i * 8 + j; + int pos = bitPos / 8; + int shift = 7 - (bitPos % 8); + if (pos < (rxlen + 7) / 8) { + data[i] |= ((rx[pos] >> shift) & 0x01) << (7 - j); + } + } + } + + // Map page address + uint8_t real_page = page; + if (page >= 64 && tag.max_page <= 64) { + real_page = page & 0x3F; // Pages above 64 map to 0-63 + } + + // Special pages + if (real_page == HITAGU_CONFIG_PADR) { + // Write config + memcpy(&tag.config, data, 4); + DBG Dbprintf("WRITE CONFIG: %02X %02X %02X %02X", data[0], data[1], data[2], data[3]); + } else if (real_page == HITAGU_PASSWORD_PADR) { + // Write password + memcpy(tag.password, data, 4); + DBG Dbprintf("WRITE PASSWORD: %02X %02X %02X %02X", data[0], data[1], data[2], data[3]); + } else if (real_page < tag.max_page) { + // Regular page + memcpy(tag.data.pages[real_page], data, 4); + DBG Dbprintf("WRITE PAGE %02X: %02X %02X %02X %02X", real_page, data[0], data[1], data[2], data[3]); + } + + // Send success acknowledgment + uint8_t ack = 0x01; // Success acknowledgment + *txlen = concatbits(tx, *txlen, &ack, 0, 8, true); + } + } + } else if (command == HITAGU_CMD_SYSINFO) { + // System info command + // Prepare system info response with ICR field + uint8_t info[8] = {0}; + + // First byte: Error flag (0) + 7 reserved bits + info[0] = 0x00; + + // Additional bytes: System Memory Block Data + // MSN (Manufacturer Serial Number) - example values + info[1] = 0x12; + info[2] = 0x34; + + // MFC (Manufacturer Code) - example value + info[3] = 0x04; // NXP + + // ICR (IC Revision) + info[4] = tag.icr; + + // Reserved bytes + info[5] = 0x00; + info[6] = 0x00; + info[7] = 0x00; + + // Add the system info data to the response + *txlen = concatbits(tx, *txlen, info, 0, 64, true); + } else if (flags == HITAGU_CMD_STAY_QUIET) { + // Quiet command - no response needed + RESET_AUTHENTICATION(); + *txlen = 0; + } else { + // Unknown command + DBG Dbprintf("Unknown command or flags: flags=%02X, cmd=%02X", flags, command); + *txlen = 0; // No response + } + + // Add CRC if requested and there is response data + if (crct_flag && *txlen > 0) { + // Calculate CRC-16/XMODEM directly from tx + uint16_t crc = Crc16(tx, *txlen, 0, CRC16_POLY_CCITT, false, true); + + // Append CRC-16 (16 bits) + *txlen = concatbits(tx, *txlen, (uint8_t *)&crc, 0, 16, true); + } +} + +/* + * Simulates a Hitag µ Tag with the given data + */ +void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol) { + + uint8_t rx[HITAG_FRAME_LEN] = {0}; + size_t rxlen = 0; + uint8_t tx[HITAG_FRAME_LEN]; + size_t txlen = 0; + + // Free any allocated BigBuf memory + BigBuf_free(); + BigBuf_Clear_ext(false); + + DbpString("Starting Hitag µ simulation"); + + // Reset tag state + memset(&tag, 0, sizeof(tag)); + tag.max_page = 64; // Default maximum page + RESET_AUTHENTICATION(); + + // Read tag data into memory if supplied + if (tag_mem_supplied) { + DbpString("Loading Hitag µ memory..."); + // First 6 bytes are the UID (48 bits) + memcpy(tag.uid, data, 6); + // Rest is page data + memcpy(tag.data.pages, data + 6, sizeof(tag.data.pages)); + } + + // Update max_page based on configuration + update_tag_max_page(); + + // Debug output of tag data + DBG Dbprintf("UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); + + for (int i = 0; i <= tag.max_page; i++) { + DBG Dbprintf("Page[%2d]: %02X %02X %02X %02X", i, tag.data.pages[i][0], tag.data.pages[i][1], + tag.data.pages[i][2], tag.data.pages[i][3]); + } + + hitag_setup_fpga(0, threshold, ledcontrol); + + int overflow = 0; + + // Simulation main loop + while ((BUTTON_PRESS() == false) && (data_available() == false)) { + uint32_t start_time = 0; + + WDT_HIT(); + + // Receive commands from the reader + hitag_tag_receive_frame(rx, sizeof(rx), &rxlen, &start_time, ledcontrol, &overflow); + + // Check if frame was captured and store it + if (rxlen > 0) { + LogTraceBits(rx, rxlen, start_time, TIMESTAMP, true); + + // Disable timer 1 with external trigger to avoid triggers during our own modulation + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + + // Prepare tag response (tx) + memset(tx, 0x00, sizeof(tx)); + txlen = 0; + + // Process received reader command + htu_handle_reader_command(rx, rxlen, tx, &txlen); + + // Wait for HITAG_T_WAIT_RESP carrier periods after the last reader bit, + // not that since the clock counts since the rising edge, but T_Wait1 is + // with respect to the falling edge, we need to wait actually (T_Wait1 - T_Low) + // periods. The gap time T_Low varies (4..10). All timer values are in + // terms of T0 units + while (AT91C_BASE_TC0->TC_CV < T0 * (HITAG_T_WAIT_RESP - HITAG_T_LOW)) { + }; + + // Send and store the tag answer (if there is any) + if (txlen > 0) { + // Transmit the tag frame + start_time = TIMESTAMP; + hitag_tag_send_frame(tx, txlen, HTU_SOF_BITS, MC4K, ledcontrol); + LogTraceBits(tx, txlen, start_time, TIMESTAMP, false); + } + + // Enable and reset external trigger in timer for capturing future frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + + // Reset the received frame and response timing info + memset(rx, 0x00, sizeof(rx)); + } + + // Reset the frame length + rxlen = 0; + // Save the timer overflow, will be 0 when frame was received + overflow += (AT91C_BASE_TC1->TC_CV / T0); + // Reset the timer to restart while-loop that receives frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG; + } + + hitag_cleanup(ledcontrol); + // Release allocated memory from BigBuf + BigBuf_free(); + + DbpString("Simulation stopped"); +} + +/* + * Send command to reader and receive answer from tag + */ +static int htu_reader_send_receive(uint8_t *tx, size_t txlen, uint8_t *rx, size_t sizeofrx, size_t *rxlen, + uint32_t t_wait, bool ledcontrol, uint8_t modulation, uint8_t sof_bits) { + // Reset the received frame + memset(rx, 0x00, sizeofrx); + + // Disable timer 1 with external trigger to avoid triggers during our own modulation + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + + DBG Dbprintf("tx %d bits:", txlen); + DBG Dbhexdump((txlen + 7) / 8, tx, false); + + // Wait until we can send the command + while (AT91C_BASE_TC0->TC_CV < T0 * t_wait) { + }; + + // Set up tracing + uint32_t start_time = TIMESTAMP; + + // Send the command - Hitag µ always requires SOF + hitag_reader_send_frame(tx, txlen, ledcontrol, true); + + // if (enable_page_tearoff && tearoff_hook() == PM3_ETEAROFF) { + // return PM3_ETEAROFF; + // } + + LogTraceBits(tx, txlen, start_time, TIMESTAMP, true); + + // Enable and reset external trigger in timer for capturing future frames + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; + + // Capture response - SOF is automatically stripped by hitag_reader_receive_frame + hitag_reader_receive_frame(rx, sizeofrx, rxlen, &start_time, ledcontrol, modulation, sof_bits); + + LogTraceBits(rx, *rxlen, start_time, TIMESTAMP, false); + DBG Dbprintf("rx %d bits:", *rxlen); + DBG Dbhexdump((*rxlen + 7) / 8, rx, false); + + // TODO: check Error flag + + return PM3_SUCCESS; +} + +/* + * Selects a tag using the READ UID, GET SYSTEM INFORMATION, and LOGIN commands + */ +static int htu_select_tag(const lf_hitag_data_t *payload, uint8_t *tx, size_t sizeoftx, uint8_t *rx, size_t sizeofrx, + int t_wait, bool ledcontrol) { + // Initialize response + size_t txlen = 0; + size_t rxlen = 0; + + // Setup FPGA and initialize + hitag_setup_fpga(FPGA_LF_EDGE_DETECT_READER_FIELD, 127, ledcontrol); + + // Prepare common flags and command variables + uint8_t flags = HITAGU_FLAG_CRCT; // Set appropriate flags for all commands + uint8_t command; + // uint8_t parameter; + + // 1. Send READ UID command + command = HITAGU_CMD_READ_UID; + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // lf cmdread -d 64 -z 96 -o 160 -e W2400 -e S224 -e E336 -s 4096 -c W0S00100010000 + + // Send the READ UID command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, t_wait, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if the response is valid + if (rxlen < 1 + 48 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Read UID command failed! %i", rxlen); + return -1; // Read UID failed + } + + // Process the UID from the response + concatbits(tag.uid, 0, rx, 1, 48, false); + + // 2. Send GET SYSTEM INFORMATION command + command = HITAGU_CMD_SYSINFO; + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send the GET SYSTEM INFORMATION command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + concatbits(&tag.icr, 0, rx, 9, 8, false); + + // Check if the response is valid + if (rxlen < 1 + 16 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + // 8265 bug? sometile lost Data field first bit + DBG Dbprintf("Get System Information command failed! %i", rxlen); + return -2; // Get System Information failed + } + + // 3. Read config block + command = HITAGU_CMD_READ_MULTIPLE_BLOCK; + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Add block address + txlen = concatbits(tx, txlen, (uint8_t *)&"\xFF", 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, (uint8_t *)&"\x00", 0, 8, true); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if the response is valid + if (rxlen < 1 + 32 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Read config block command failed! %i", rxlen); + return -2; // Read config block failed + } + + // Process the config block from the response + concatbits(tag.config.asBytes, 0, rx, 1, 32, false); + reverse_arraybytes(tag.config.asBytes, HITAGU_BLOCK_SIZE); + + // 4. Send LOGIN command if necessary + if (payload && (payload->cmd == HTUF_82xx || payload->cmd == HTUF_PASSWORD)) { + command = HITAGU_CMD_LOGIN; // Set command for login + txlen = 0; // Reset txlen for the new command + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + txlen = concatbits(tx, txlen, payload->pwd, 0, HITAG_PASSWORD_SIZE * 8, false); + + // Append CRC-16 (16 bits) + crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send the LOGIN command and receive the response + htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check if login succeeded + if (rxlen < 1 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Login command failed! %i", rxlen); + return -3; // Login failed + } else { + DBG DbpString("Login successful"); + } + + // flags |= HITAGU_FLAG_ADR; + // command = HITAGU_CMD_LOGIN; // Set command for login + // txlen = 0; // Reset txlen for the new command + // txlen = concatbits(tx, txlen, &flags, 0, 5, true); + // txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // txlen = concatbits(tx, txlen, payload->uid, 0, HITAGU_UID_SIZE * 8, false); + // txlen = concatbits(tx, txlen, payload->pwd, 0, HITAG_PASSWORD_SIZE * 8, false); + + // // Append CRC-16 (16 bits) + // crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + // txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // // Send the LOGIN command and receive the response + // htu_reader_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // // Check if login succeeded + // if (rxlen < 1 + 16 || Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + // DBG Dbprintf("Login command failed! %i", rxlen); + // return -3; // Login failed + // } else { + // DbpString("Login successful"); + // } + } + + // If all commands are successful, update the tag's state + update_tag_max_page(); // Update max_page based on the new configuration + + return 0; // Selection successful +} + +/* + * Reads the UID of a Hitag µ tag using the INVENTORY command + */ +int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer) { + // Initialize response + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // Use htu_select_tag to select the card and retrieve UID + int reason = htu_select_tag(NULL, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + DBG DbpString("Error: htu_read_uid Failed to select tag"); + status = PM3_ERFTRANS; + goto exit; + } + + DBG Dbprintf("HitagU UID: %02X%02X%02X%02X%02X%02X", tag.uid[0], tag.uid[1], tag.uid[2], tag.uid[3], tag.uid[4], tag.uid[5]); + + if (uid) { + *uid = BSWAP_48(*(uint64_t*)&tag.uid); + } + +exit: + hitag_cleanup(ledcontrol); + + if (send_answer) { + // Send UID + reply_reason(CMD_LF_HITAGU_UID, status, reason, tag.uid, sizeof(tag.uid)); + } + + // Reset authentication state + RESET_AUTHENTICATION(); + + return status; +} + +/* + * Reads a Hitag µ tag + */ +void htu_read(const lf_hitag_data_t *payload, bool ledcontrol) { + size_t rxlen = 0; + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + size_t txlen = 0; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // DBG { + // DbpString("htu_read"); + // Dbprintf("payload->page: %d", payload->page); + // Dbprintf("payload->page_count: %d", payload->page_count); + // Dbprintf("payload->cmd: %d", payload->cmd); + // Dbprintf("payload->uid: %02X%02X%02X%02X%02X%02X", payload->uid[0], payload->uid[1], payload->uid[2], + // payload->uid[3], payload->uid[4], payload->uid[5]); + // Dbprintf("payload->key: %02X%02X%02X%02X%02X%02X", payload->key[0], payload->key[1], payload->key[2], + // payload->key[3], payload->key[4], payload->key[5]); + // Dbprintf("payload->pwd: %02X%02X%02X%02X", payload->pwd[0], payload->pwd[1], payload->pwd[2], payload->pwd[3]); + // } + + // Use htu_select_tag to select the card and retrieve UID + int reason = htu_select_tag(payload, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + DbpString("Error: htu_read Failed to select tag"); + status = PM3_ERFTRANS; + goto exit; + } + + lf_htu_read_response_t card = { + .icr = tag.icr, + }; + memcpy(card.uid, tag.uid, HITAGU_UID_SIZE); + memcpy(card.config_page.asBytes, tag.config.asBytes, HITAGU_BLOCK_SIZE); + + uint8_t page_count_index = payload->page_count - 1; + + if (payload->page_count == 0) { + page_count_index = tag.max_page - payload->page - 1; + } + + // Step 2: Process the actual command + // Add flags (5 bits) + uint8_t flags = HITAGU_FLAG_CRCT; + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + + // Read command format: [UID] [CRC-16] + + // Add command (6 bits) + uint8_t command = HITAGU_CMD_READ_MULTIPLE_BLOCK; + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // The 8265 chip has known issues when reading multiple blocks: + // - page_count = 1: Works correctly + // - page_count >= 2: Data field is left shifted by 1 bit + // - page_count = 2 or page+page_count exceeds valid page: Data field has an extra '1' bit + // - page_count = 3,4: Data field last bit is always '1' + // - page_count = 5: CRC is not appended + // - page_count >= 6: May cause next command to have no response + // Workaround: Read one block at a time + if (payload->mode == 0 /**for debug */ + && (payload->cmd == HTUF_82xx || tag.icr == HITAGU_ICR_8265 || !memcmp(tag.uid, "\x00\x00\x00\x00\x00\x00", 6))) { + + uint8_t page_addr; + for (int i = 0; i <= page_count_index; i++) { + page_addr = payload->page + i; + + txlen = 5 + 6; // restore txlen for the new command + txlen = concatbits(tx, txlen, &page_addr, 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, (uint8_t *)&"\x00", 0, 8, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + if (flags & HITAGU_FLAG_CRCT && Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Error: response CRC invalid"); + card.pages_reason[i] = -1; + continue; + } + + // Check response + if (rxlen < 1 + HITAGU_BLOCK_SIZE * 8 + (flags & HITAGU_FLAG_CRCT ? 16 : 0)) { + DbpString("Error: invalid response received after read command"); + card.pages_reason[i] = -11; + } else { + DBG Dbprintf("Read successful, response: %d bits", rxlen); + // todo: For certain pages, update our cached data + card.pages_reason[i] = 1; + concatbits(card.pages[i], 0, rx, 1, HITAGU_BLOCK_SIZE * 8, false); + } + } + } else { + txlen = concatbits(tx, txlen, &payload->page, 0, 8, true); + + // Add number of blocks, 0 means 1 block + txlen = concatbits(tx, txlen, &page_count_index, 0, 8, true); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + // Send read command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + if (flags & HITAGU_FLAG_CRCT && Crc16(rx, rxlen, 0, CRC16_POLY_CCITT, false, false) != 0) { + DBG Dbprintf("Error: response CRC invalid"); + status = PM3_ERFTRANS; + goto exit; + } + + // Check response + if (rxlen < 1 + HITAGU_BLOCK_SIZE * 8 + (flags & HITAGU_FLAG_CRCT ? 16 : 0)) { + DbpString("Error: invalid response received after read command"); + status = PM3_ERFTRANS; + } else { + DBG Dbprintf("Read successful, response: %d bits", rxlen); + // todo: For certain pages, update our cached data + concatbits((uint8_t *)card.pages, 0, rx, 1, rxlen - 1 - 16, false); + for (int i = 0; i < (rxlen - 1 - 16) / (HITAGU_BLOCK_SIZE * 8); i++) { + card.pages_reason[i] = 1; + } + } + } + +exit: + hitag_cleanup(ledcontrol); + // Send status to client + reply_reason(CMD_LF_HITAGU_READ, status, reason, (uint8_t *)&card, sizeof(card)); +} + +/* + * Writes a page to a Hitag µ tag + */ +void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol) { + size_t rxlen = 0; + uint8_t rx[HITAG_FRAME_LEN] = {0x00}; + size_t txlen = 0; + uint8_t tx[HITAG_FRAME_LEN] = {0x00}; + int status = PM3_SUCCESS; + + // DBG { + // DbpString("htu_write_page"); + // Dbprintf("payload->page: %d", payload->page); + // Dbprintf("payload->data: %02X%02X%02X%02X", payload->data[0], payload->data[1], payload->data[2], payload->data[3]); + // Dbprintf("payload->cmd: %d", payload->cmd); + // Dbprintf("payload->uid: %02X%02X%02X%02X%02X%02X", payload->uid[0], payload->uid[1], payload->uid[2], payload->uid[3], payload->uid[4], payload->uid[5]); + // Dbprintf("payload->key: %02X%02X%02X%02X%02X%02X", payload->key[0], payload->key[1], payload->key[2], payload->key[3], payload->key[4], payload->key[5]); + // Dbprintf("payload->pwd: %02X%02X%02X%02X", payload->pwd[0], payload->pwd[1], payload->pwd[2], payload->pwd[3]); + // Dbprintf("payload->mode: %d", payload->mode); + // } + + int reason = htu_select_tag(payload, tx, ARRAYLEN(tx), rx, ARRAYLEN(rx), HITAG_T_WAIT_FIRST, ledcontrol); + if (reason != 0) { + status = PM3_ERFTRANS; + goto exit; + } + + // Step 2: Send write command + uint8_t flags = HITAGU_FLAG_CRCT; + + // Add flags (5 bits) for write operation + txlen = concatbits(tx, txlen, &flags, 0, 5, true); + + // Add write command (6 bits) + uint8_t command = HITAGU_CMD_WRITE_SINGLE_BLOCK; + txlen = concatbits(tx, txlen, &command, 0, 6, true); + + // Add page address (8 bits) + txlen = concatbits(tx, txlen, &payload->page, 0, 8, true); + + // Add data to write (32 bits) + txlen = concatbits(tx, txlen, payload->data, 0, 32, false); + + // Append CRC-16 (16 bits) + uint16_t crc = Crc16(tx, txlen, 0, CRC16_POLY_CCITT, false, true); + txlen = concatbits(tx, txlen, (uint8_t *)&crc, 0, 16, true); + + DBG Dbprintf("Writing to page 0x%02X", payload->page); + + // Send write command and receive response + htu_reader_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, MC4K, HTU_SOF_BITS); + + // Check response + if (payload->cmd == HTUF_82xx && rxlen == 0) { + // 8265 bug? no response on successful write command + reason = 0; + status = PM3_ENODATA; + } else if (rxlen != 1 + 16) { + DbpString("Error: htu_write_page No valid response received after write command"); + reason = -1; + status = PM3_ERFTRANS; + } else { + DBG Dbprintf("Write successful, response: %d bits", rxlen); + } + +exit: + hitag_cleanup(ledcontrol); + reply_reason(CMD_LF_HITAGU_WRITE, status, reason, NULL, 0); +} diff --git a/armsrc/hitagu.h b/armsrc/hitagu.h new file mode 100644 index 000000000..a1144e1e7 --- /dev/null +++ b/armsrc/hitagu.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Hitag µ functions +//----------------------------------------------------------------------------- + +#ifndef _HITAGU_H_ +#define _HITAGU_H_ + +#include "common.h" +#include "hitag.h" + +void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol); +void htu_read(const lf_hitag_data_t *payload, bool ledcontrol); +void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol); +int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer); + +#endif diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 274024fb7..854828df9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -380,6 +380,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfhid.c ${PM3_ROOT}/client/src/cmdlfhitag.c ${PM3_ROOT}/client/src/cmdlfhitaghts.c + ${PM3_ROOT}/client/src/cmdlfhitagu.c ${PM3_ROOT}/client/src/cmdlfidteck.c ${PM3_ROOT}/client/src/cmdlfindala.c ${PM3_ROOT}/client/src/cmdlfio.c diff --git a/client/Makefile b/client/Makefile index cf4b65b5f..f743d6968 100644 --- a/client/Makefile +++ b/client/Makefile @@ -660,6 +660,7 @@ SRCS = mifare/aiddesfire.c \ cmdlfhid.c \ cmdlfhitag.c \ cmdlfhitaghts.c \ + cmdlfhitagu.c \ cmdlfidteck.c \ cmdlfindala.c \ cmdlfio.c \ diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 1e17d3361..7d0c952b1 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -380,6 +380,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfhid.c ${PM3_ROOT}/client/src/cmdlfhitag.c ${PM3_ROOT}/client/src/cmdlfhitaghts.c + ${PM3_ROOT}/client/src/cmdlfhitagu.c ${PM3_ROOT}/client/src/cmdlfidteck.c ${PM3_ROOT}/client/src/cmdlfindala.c ${PM3_ROOT}/client/src/cmdlfio.c diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index c38e6da03..ae5f8b845 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -83,7 +83,7 @@ int CmdHFSearch(const char *Cmd) { int res = PM3_ESOFT; - uint8_t success[20] = {0}; + uint8_t success[COUNT_OF_PROTOCOLS] = {0}; PROMPT_CLEARLINE; PrintAndLogEx(INPLACE, " Searching for ThinFilm tag..."); diff --git a/client/src/cmdlfhitag.c b/client/src/cmdlfhitag.c index 2cafdb161..81e38edc4 100644 --- a/client/src/cmdlfhitag.c +++ b/client/src/cmdlfhitag.c @@ -16,6 +16,8 @@ // Low frequency Hitag support //----------------------------------------------------------------------------- #include "cmdlfhitag.h" +#include "cmdlfhitaghts.h" +#include "cmdlfhitagu.h" #include #include "cmdparser.h" // command_t #include "comms.h" @@ -31,7 +33,6 @@ #include "pm3_cmd.h" // return codes #include "hitag2/hitag2_crypto.h" #include "util_posix.h" // msclock -#include "cmdlfhitaghts.h" static int CmdHelp(const char *Cmd); @@ -2458,6 +2459,7 @@ static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdLFHitagList, AlwaysAvailable, "List Hitag trace history"}, {"hts", CmdLFHitagS, AlwaysAvailable, "{ Hitag S/8211 operations }"}, + {"htu", CmdLFHitagU, AlwaysAvailable, "{ Hitag µ/8265 operations }"}, {"-----------", CmdHelp, IfPm3Hitag, "------------------------ " _CYAN_("General") " ------------------------"}, {"info", CmdLFHitagInfo, IfPm3Hitag, "Hitag 2 tag information"}, {"reader", CmdLFHitagReader, IfPm3Hitag, "Act like a Hitag 2 reader"}, diff --git a/client/src/cmdlfhitagu.c b/client/src/cmdlfhitagu.c new file mode 100644 index 000000000..0915037fa --- /dev/null +++ b/client/src/cmdlfhitagu.c @@ -0,0 +1,687 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency Hitag µ support +//----------------------------------------------------------------------------- +#include "cmdlfhitagu.h" + +#include "cliparser.h" +#include "cmddata.h" // setDemodBuff +#include "cmdparser.h" // command_t +#include "cmdtrace.h" +#include "commonutil.h" +#include "comms.h" +#include "crc16.h" +#include "fileutils.h" // savefile +#include "graph.h" // MAX_GRAPH_TRACE_LEN +#include "hitag.h" +#include "hitag2/hitag2_crypto.h" +#include "lfdemod.h" +#include "pm3_cmd.h" // return codes +#include "protocols.h" // defines +#include "util_posix.h" // msclock +#include + +static int CmdHelp(const char *Cmd); + +uint8_t hitagu_CRC_check(uint8_t *d, uint32_t nbit) { + if (nbit < 9) { + return 2; + } + + return (Crc16(d, nbit, 0, CRC16_POLY_CCITT, false, false) == 0); +} + +void annotateHitagU(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { + + if (is_response) { + + } else { + uint8_t flag = reflect8(cmd[0]) & 0x1F; + uint8_t command = ((reflect8(cmd[0]) >> 5) & 0x07) | ((reflect8(cmd[1]) & 0x07) << 3); + bool has_uid = false; + + size_t exp_len = snprintf(exp, size, "Flg:"); + + if (flag & HITAGU_FLAG_PEXT) { + exp_len += snprintf(exp + exp_len, size - exp_len, " PEXT"); + } + if (flag & HITAGU_FLAG_INV) { + exp_len += snprintf(exp + exp_len, size - exp_len, " INV"); + if (flag & HITAGU_FLAG_RFU) { + exp_len += snprintf(exp + exp_len, size - exp_len, " RFU"); + } + if (flag & HITAGU_FLAG_NOS) { + exp_len += snprintf(exp + exp_len, size - exp_len, " NOS"); + } + } else { + if (flag & HITAGU_FLAG_SEL) { + exp_len += snprintf(exp + exp_len, size - exp_len, " SEL"); + } + if (flag & HITAGU_FLAG_ADR) { + exp_len += snprintf(exp + exp_len, size - exp_len, " ADR"); + has_uid = true; + } + } + if (flag & HITAGU_FLAG_CRCT) { + exp_len += snprintf(exp + exp_len, size - exp_len, " CRCT"); + } + + exp_len += snprintf(exp + exp_len, size - exp_len, "|Cmd: "); + switch (command) { + case HITAGU_CMD_LOGIN: { + bool has_mfc = false; + if (cmdsize == 6 + has_uid * HITAGU_UID_SIZE || cmdsize == 8 + has_uid * HITAGU_UID_SIZE) { + exp_len += snprintf(exp + exp_len, size - exp_len, "8265 LOGIN"); + } else if (cmdsize == 7 + has_uid * HITAGU_UID_SIZE || cmdsize == 9 + has_uid * HITAGU_UID_SIZE) { + uint8_t mfc; + concatbits(&mfc, 0, cmd, 5 + 6 + 8 + 32, 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, "LOGIN mfc:%02x ", mfc); + has_mfc = true; + } + if (has_uid) { + uint8_t uid[HITAGU_UID_SIZE]; + concatbits(uid, 0, cmd, 5 + 6 + has_mfc * 8 + 32, HITAGU_UID_SIZE * 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, " uid:%s", sprint_hex_inrow(uid, HITAGU_UID_SIZE)); + } + uint8_t password[HITAG_PASSWORD_SIZE]; + concatbits(password, 0, cmd, 5 + 6 + has_mfc * 8 + has_uid * HITAGU_UID_SIZE * 8, HITAG_PASSWORD_SIZE * 8, false); + exp_len += snprintf(exp + exp_len, size - exp_len, " pwd:%s", sprint_hex_inrow(password, HITAG_PASSWORD_SIZE)); + break; + } + case HITAGU_CMD_INVENTORY: + exp_len += snprintf(exp + exp_len, size - exp_len, "INVENTORY"); + break; + case HITAGU_CMD_READ_MULTIPLE_BLOCK: { + uint8_t block_addr; + concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); + + uint8_t block_count; + concatbits(&block_count, 0, cmd, 5 + 6 + 8, 8, false); + + exp_len += snprintf(exp + exp_len, size - exp_len, "READ MULTIPLE BLOCK start:%d num:%d", reflect8(block_addr), reflect8(block_count)); + break; + } + case HITAGU_CMD_WRITE_SINGLE_BLOCK: { + uint8_t block_addr; + concatbits(&block_addr, 0, cmd, 5 + 6, 8, false); + + uint8_t block_data[4]; + concatbits(block_data, 0, cmd, 5 + 6 + 8, 32, false); + + exp_len += snprintf(exp + exp_len, size - exp_len, "WRITE SINGLE BLOCK start:%d data:[%s]", + reflect8(block_addr), sprint_hex_inrow(block_data, 4)); + break; + } + case HITAGU_CMD_SELECT: + exp_len += snprintf(exp + exp_len, size - exp_len, "SELECT"); + break; + case HITAGU_CMD_SYSINFO: + exp_len += snprintf(exp + exp_len, size - exp_len, "GET SYSTEM INFORMATION"); + break; + case HITAGU_CMD_READ_UID: + exp_len += snprintf(exp + exp_len, size - exp_len, "READ UID"); + break; + case HITAGU_CMD_STAY_QUIET: + exp_len += snprintf(exp + exp_len, size - exp_len, "STAY QUIET"); + break; + default: + exp_len += snprintf(exp + exp_len, size - exp_len, "Unknown 0x%02X", command); + break; + } + } +} + +static bool htu_get_uid(uint64_t *uid) { + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_UID, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_UID, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return false; + } + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag µ UID"); + return false; + } + + if (uid) { + *uid = bytes_to_num(resp.data.asBytes, HITAGU_UID_SIZE); + } + return true; +} + +int read_htu_uid(void) { + uint64_t uid = 0; + if (htu_get_uid(&uid) == false) { + return PM3_ESOFT; + } + + PrintAndLogEx(SUCCESS, "UID.... " _GREEN_("%012llX"), uid); + // PrintAndLogEx(SUCCESS, "TYPE... " _GREEN_("%s"), htu_get_type_str(uid)); + return PM3_SUCCESS; +} + +static int process_hitagu_common_args(CLIParserContext *ctx, lf_hitag_data_t *const packet) { + bool use_82xx = arg_get_lit(ctx, 1); + bool use_password = false; + uint8_t key[HITAG_PASSWORD_SIZE]; + int key_len = 0; + + int res = CLIParamHexToBuf(arg_get_str(ctx, 2), key, HITAG_PASSWORD_SIZE, &key_len); + if (res != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + if (key_len != 0 && key_len != HITAG_PASSWORD_SIZE) { + PrintAndLogEx(WARNING, "Wrong KEY len expected 0 or 4, got %d", key_len); + return PM3_EINVARG; + } + + // complete options + if (key_len == 0 && use_82xx) { + memcpy(key, "\x00\x00\x00\x00", 4); + key_len = 4; + } else if (key_len != 0) { + use_password = true; + } + + memset(packet, 0, sizeof(*packet)); + + if (use_82xx) { + packet->cmd = HTUF_82xx; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in 82xx mode"); + } else if (use_password) { + packet->cmd = HTUF_PASSWORD; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag µ") " in password mode"); + } else { + packet->cmd = HTUF_PLAIN; + memcpy(packet->pwd, key, sizeof(packet->pwd)); + PrintAndLogEx(INFO, "Access " _YELLOW_("Hitag µ") " in Plain mode"); + } + + return PM3_SUCCESS; +} + +static void print_error(int8_t reason) { + switch (reason) { + case 0: + PrintAndLogEx(FAILED, "No data"); + break; + case -2: + PrintAndLogEx(FAILED, "UID Request failed!"); + break; + case -3: + PrintAndLogEx(FAILED, "Select UID failed!"); + break; + case -4: + PrintAndLogEx(FAILED, "No write access on block. Not authorized?"); + break; + case -5: + PrintAndLogEx(FAILED, "Write failed! Wrong password?"); + break; + case -6: + PrintAndLogEx(FAILED, "Error, " _YELLOW_("AUT=1") " This tag is configured in Authentication Mode"); + break; + case -7: + PrintAndLogEx(FAILED, "Error, unknown function"); + break; + case -8: + PrintAndLogEx(FAILED, "Authenticate failed!"); + break; + case -9: + PrintAndLogEx(FAILED, "No write access on block"); + break; + case -10: + PrintAndLogEx(FAILED, "Write to block failed!"); + break; + case -11: + PrintAndLogEx(FAILED, "Read block failed!"); + break; + default: + // PM3_REASON_UNKNOWN + PrintAndLogEx(FAILED, "Error - Hitag µ failed"); + } +} + +static void hitagu_config_print(hitagu_config_t config) { + PrintAndLogEx(INFO, " Data Rate......... %s", (const char *[]) {"2 kbit/s", "4 kbit/s", "8 kbit/s", "Reserved"}[config.datarate]); + PrintAndLogEx(INFO, " Encoding.......... %s", config.encoding ? _YELLOW_("Bi-phase") : _YELLOW_("Manchester")); + PrintAndLogEx(INFO, " Password Protect W Bit 0-127(block 0-3) %s", config.pwdW0_127 ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect W Bit 128-511(block 4-15) %s", config.pwdW128_511 ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect W Bit 512-Max(block 16-Max) %s", config.pwdW512_max ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Password Protect RW Bit 512-Max(block 16-Max) %s", config.pwdRW512_max ? _RED_("Yes") : _GREEN_("No")); +} + +static void hitagu8265_config_print(hitagu82xx_config_t config) { + PrintAndLogEx(INFO, " Config Byte0: %s", sprint_hex((uint8_t *)&config, sizeof(config))); // for debug + // Check if datarate_override is set + if (config.datarate_override) { + PrintAndLogEx(INFO, " Data Rate........ %s", _YELLOW_("2 kbit/s")); + } else { + PrintAndLogEx(INFO, " Data Rate........ %s", + (const char *[]) {"2 kbit/s", "4 kbit/s", "8 kbit/s", "2 kbit/s"}[config.datarate]); + } + PrintAndLogEx(INFO, " Rate Override.... %s", config.datarate_override ? _RED_("Yes") : _GREEN_("No")); + PrintAndLogEx(INFO, " Encoding......... %s", config.encoding ? _YELLOW_("Bi-phase") : _YELLOW_("Manchester")); + + PrintAndLogEx(INFO, " TTF mode ........ %s", + (const char *[]) { + "Block 0, Block 1, Block 2, Block 3", + "Block 0, Block 1", + "Block 0, Block 1, Block 2, Block 3", + "Block 0, Block 1, Block 2, Block 3", + }[config.ttf_mode]); + PrintAndLogEx(INFO, " TTF.............. %s", config.ttf ? _GREEN_("Enabled") : _RED_("Disabled")); +} + +static int CmdLFHitagURead(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu rdbl", + "Read Hitag µ memory.\n\n" + " 82xx password mode: \n" + " - default password 00000000\n", + " lf hitag htu rdbl -p 1 -> Hitag µ, plain mode\n" + " lf hitag htu rdbl -p 1 --82xx -> 82xx, password mode, def pass\n" + " lf hitag htu rdbl -p 1 --82xx -k 9AC4999C -> 82xx, password mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_int0("p", "page", "", "block address to read from (def: 0)"), + arg_int0("c", "count", "", "how many blocks to read. '0' reads all blocks (def: 1)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + lf_hitag_data_t packet; + + if (process_hitagu_common_args(ctx, &packet) < 0) return PM3_EINVARG; + + uint32_t page = arg_get_int_def(ctx, 3, 0); + + if (page >= HITAGU_MAX_BLOCKS) { + PrintAndLogEx(WARNING, "Block address Invalid. Maximum is 255."); + return PM3_EINVARG; + } + + uint32_t count = arg_get_int_def(ctx, 4, 1); + + if (count > HITAGU_MAX_BLOCKS) { + PrintAndLogEx(WARNING, "No more than %d blocks can be read at once.", HITAGU_MAX_BLOCKS); + return PM3_EINVARG; + } + + CLIParserFree(ctx); + + packet.page = page; + packet.page_count = count; + // packet.mode = 1; // for debug + + PrintAndLogEx(INFO, "Read Hitag µ memory block %d, count %d", page, count); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return PM3_ESOFT; + } + + lf_htu_read_response_t *card = (lf_htu_read_response_t *)resp.data.asBytes; + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + + int user_blocks; + uint8_t icr = card->icr; + + if (icr == HITAGU_ICR_STANDARD) { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED; + PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; + PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_8265) { + user_blocks = HITAGU_MAX_PAGE_8265; + PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); + } + + if (packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) { + hitagu8265_config_print(card->config_page.s82xx); + } else { + hitagu_config_print(card->config_page.s); + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Data") " ---------------------------"); + PrintAndLogEx(INFO, " # | 00 01 02 03 | ascii | perm | info"); + PrintAndLogEx(INFO, "----+-------------+-------+------+------"); + + if (count == 0) { + count = (user_blocks > page) ? (user_blocks - page) : HITAGU_MAX_BLOCKS; + } + + for (int i = 0; i < count; ++i) { + int page_addr = page + i; + if (page_addr >= HITAGU_MAX_BLOCKS) { + break; + } + if (card->pages_reason[i] > 0) { + PrintAndLogEx(SUCCESS, "% 3u | %s | " NOLF, page_addr, sprint_hex_ascii(card->pages[i], HITAGU_BLOCK_SIZE)); + + // access right + // 82xx + if ((packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) && page_addr != HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, _YELLOW_("RO ") NOLF); + } else if ((packet.cmd == HTUF_82xx || icr == HITAGU_ICR_8265) && page_addr == HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); + // Hitag µ + } else if (page_addr < HITAGU_MAX_PAGE_STANDARD) { + if (card->config_page.s.pwdW0_127) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } else if (HITAGU_MAX_PAGE_STANDARD <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED) { + if (card->config_page.s.pwdW128_511) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } else if (HITAGU_MAX_PAGE_ADVANCED <= page_addr && page_addr < HITAGU_MAX_PAGE_ADVANCED_PLUS) { + if (card->config_page.s.pwdRW512_max) { + PrintAndLogEx(NORMAL, _RED_("R/WP") NOLF); + } else { + if (card->config_page.s.pwdW512_max) + PrintAndLogEx(NORMAL, _RED_("RO ") NOLF); + else + PrintAndLogEx(NORMAL, _GREEN_("RW ") NOLF); + } + } else + PrintAndLogEx(NORMAL, _YELLOW_("UNK ") NOLF); + + PrintAndLogEx(NORMAL, " | " NOLF); + + // info + if (page_addr == HITAGU_PASSWORD_PADR) { + PrintAndLogEx(NORMAL, "Password"); + } else if (page_addr == HITAGU_CONFIG_PADR) { + PrintAndLogEx(NORMAL, "Config"); + } else + PrintAndLogEx(NORMAL, "Data"); + } else { + PrintAndLogEx(INFO, "% 3u | -- -- -- -- | .... | N/A | " NOLF, page_addr); + print_error(card->pages_reason[i]); + } + } + + PrintAndLogEx(INFO, "----+-------------+-------+------+------"); + PrintAndLogEx(INFO, " " _YELLOW_("RO") " = Read without password, write with password"); + PrintAndLogEx(INFO, " " _GREEN_("R/W") " = Read and write without password"); + PrintAndLogEx(INFO, " " _RED_("R/WP") " = Read and write with password"); + PrintAndLogEx(INFO, "----------------------------------------"); + return PM3_SUCCESS; +} + +static int CmdLFHitagUDump(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu dump", + "Read all Hitag µ memory and save to file\n" + " 82xx password mode: \n" + " - default password 00000000\n", + "lf hitag htu dump --82xx -> use def pwd\n" + "lf hitag htu dump --82xx -k 9AC4999C -> pwd mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_str0("f", "file", "", "specify file name"), + arg_lit0(NULL, "ns", "no save to file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + lf_hitag_data_t packet; + memset(&packet, 0, sizeof(packet)); + + if (process_hitagu_common_args(ctx, &packet) < 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + + bool nosave = arg_get_lit(ctx, 4); + CLIParserFree(ctx); + + // read all pages + packet.page = 0; + packet.page_count = 0; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 5000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return PM3_ESOFT; + } + + lf_htu_read_response_t *card = (lf_htu_read_response_t *)resp.data.asBytes; + + int user_blocks; + uint8_t icr = card->icr; + + if (icr == HITAGU_ICR_STANDARD) { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Hitag μ Standard (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED; + PrintAndLogEx(INFO, "Hitag μ Advanced (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_ADVANCED_PLUS) { + user_blocks = HITAGU_MAX_PAGE_ADVANCED_PLUS; + PrintAndLogEx(INFO, "Hitag μ Advanced+ (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else if (icr == HITAGU_ICR_8265) { + user_blocks = HITAGU_MAX_PAGE_8265; + PrintAndLogEx(INFO, "Hitag μ 8265 (ICR=0x%02X), user blocks: 0x%02X", icr, user_blocks); + } else { + user_blocks = HITAGU_MAX_PAGE_STANDARD; + PrintAndLogEx(INFO, "Unknown ICR (0x%02X)", icr); + } + + int mem_size = (user_blocks + 2) * HITAGU_BLOCK_SIZE; + + hitagu_config_t config = card->config_page.s; + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + hitagu_config_print(config); + + if (nosave) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Called with no save option"); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; + } + + if (fnlen < 1) { + char *fptr = filename; + fptr += snprintf(filename, sizeof(filename), "lf-hitagu-"); + FillFileNameByUID(fptr, card->uid, "-dump", HITAGU_UID_SIZE); + } + + pm3_save_dump(filename, (uint8_t *)card->pages, mem_size, jsfHitag); + + return PM3_SUCCESS; +} + +static int CmdLFHitagUWrite(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu wrbl", + "Write a block in Hitag µ memory.\n" + " 82xx password mode: \n" + " - default password 00000000\n", + " lf hitag htu wrbl -p 6 -d 01020304 -> Hitag µ, plain mode\n" + " lf hitag htu wrbl -p 6 -d 01020304 --82xx -> use def pwd\n" + " lf hitag htu wrbl -p 6 -d 01020304 --82xx -k 9AC4999C -> 82xx, password mode\n"); + + void *argtable[] = {arg_param_begin, + arg_lit0("8", "82xx", "82xx mode"), + arg_str0("k", "key", "", "pwd, 4 hex bytes"), + arg_int1("p", "page", "", "block address to write to"), + arg_str1("d", "data", "", "data, 4 hex bytes"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + lf_hitag_data_t packet; + + if (process_hitagu_common_args(ctx, &packet) < 0) return PM3_EINVARG; + + int page = arg_get_int_def(ctx, 3, 0); + + uint8_t data[HITAGU_BLOCK_SIZE]; + int data_len = 0; + + int res = CLIParamHexToBuf(arg_get_str(ctx, 4), data, HITAGU_BLOCK_SIZE, &data_len); + if (res != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + CLIParserFree(ctx); + + packet.page = page; + memcpy(packet.data, data, sizeof(packet.data)); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_HITAGU_WRITE, (uint8_t *)&packet, sizeof(packet)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_LF_HITAGU_WRITE, &resp, 4000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_ETEAROFF) { + PrintAndLogEx(INFO, "Writing tear off triggered"); + return PM3_SUCCESS; + } + + if (resp.status != PM3_SUCCESS) { + print_error(resp.reason); + return resp.status; + } + + PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )"); + return PM3_SUCCESS; +} + +static int CmdLFHitagUReader(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu reader", "Act as a Hitag µ reader. Look for Hitag µ tags until Enter or the pm3 button is pressed\n", + "lf hitag htu reader\n" + "lf hitag htu reader -@ -> Continuous mode"); + + void *argtable[] = {arg_param_begin, arg_lit0("@", NULL, "continuous reader mode"), arg_param_end}; + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool cm = arg_get_lit(ctx, 1); + CLIParserFree(ctx); + + if (cm) { + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + } + + do { + // read UID + read_htu_uid(); + } while (cm && kbd_enter_pressed() == false); + + return PM3_SUCCESS; +} + +static int CmdLFHitagUSim(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf hitag htu sim", + "Simulate Hitag µ transponder\n" + "You need to `lf hitag htu eload` first", + "lf hitag htu sim\n" + "lf hitag htu sim --82xx\n" + "lf hitag htu sim -t 30 -> set threshold to 30"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("8", "82xx", "simulate 82xx"), + arg_int0("t", "threshold", "", "set edge detect threshold (def: 127)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + // bool use_82xx = arg_get_lit(ctx, 1); // not implemented yet + int threshold = arg_get_int_def(ctx, 2, 127); + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandMIX(CMD_LF_HITAGU_SIMULATE, false, threshold, 0, NULL, 0); + return PM3_SUCCESS; +} + +static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "hitagu"); } + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help" }, + {"list", CmdLFHitagUList, AlwaysAvailable, "List Hitag µ trace history" }, + {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("General") " ------------------------" }, + {"reader", CmdLFHitagUReader, IfPm3Hitag, "Act like a Hitag µ reader" }, + {"rdbl", CmdLFHitagURead, IfPm3Hitag, "Read Hitag µ block" }, + {"dump", CmdLFHitagUDump, IfPm3Hitag, "Dump Hitag µ blocks to a file" }, + {"wrbl", CmdLFHitagUWrite, IfPm3Hitag, "Write Hitag µ block" }, + {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Simulation") " -----------------------"}, + {"sim", CmdLFHitagUSim, IfPm3Hitag, "Simulate Hitag µ transponder" }, + {NULL, NULL, 0, NULL } +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdLFHitagU(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdlfhitagu.h b/client/src/cmdlfhitagu.h new file mode 100644 index 000000000..af2153083 --- /dev/null +++ b/client/src/cmdlfhitagu.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Low frequency Hitag µ support +//----------------------------------------------------------------------------- + +#ifndef CMDLFHITAGU_H__ +#define CMDLFHITAGU_H__ + +#include "common.h" +#include "hitag.h" + +uint8_t hitagu_CRC_check(uint8_t *d, uint32_t nbit); +void annotateHitagU(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response); + +int CmdLFHitagU(const char *Cmd); + +int read_htu_uid(void); + +#endif //CMDLFHITAGU_H__ diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 1d0344258..d563d4478 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -29,6 +29,7 @@ #include "fileutils.h" // for saveFile #include "cmdlfhitag.h" // annotate hitag #include "cmdlfhitaghts.h" // annotate hitags +#include "cmdlfhitagu.h" // annotate hitagu #include "pm3_cmd.h" // tracelog_hdr_t #include "cliparser.h" // args.. @@ -586,8 +587,12 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case PROTO_HITAG1: case PROTO_HITAGS: crcStatus = hitag1_CRC_check(frame, (data_len * 8) - ((8 - parityBytes[0]) % 8)); - case PROTO_CRYPTORF: + break; + case PROTO_HITAGU: + crcStatus = hitagu_CRC_check(frame, (data_len * 8) - ((8 - parityBytes[0]) % 8)); + break; case PROTO_HITAG2: + case PROTO_CRYPTORF: default: break; } @@ -625,6 +630,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr && protocol != PROTO_HITAG1 && protocol != PROTO_HITAG2 && protocol != PROTO_HITAGS + && protocol != PROTO_HITAGU && protocol != THINFILM && protocol != FELICA && protocol != LTO @@ -647,7 +653,7 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr snprintf(line[j / 18] + ((j % 18) * 4), 120, "%02X! ", frame[j]); } - } else if (((protocol == PROTO_HITAG1) || (protocol == PROTO_HITAG2) || (protocol == PROTO_HITAGS))) { + } else if (((protocol == PROTO_HITAG1) || (protocol == PROTO_HITAG2) || (protocol == PROTO_HITAGS) || (protocol == PROTO_HITAGU))) { if (j == 0) { @@ -803,6 +809,9 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case PROTO_HITAGS: annotateHitagS(explanation, sizeof(explanation), frame, (data_len * 8) - ((8 - parityBytes[0]) % 8), hdr->isResponse); break; + case PROTO_HITAGU: + annotateHitagU(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); + break; case ICLASS: annotateIclass(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); break; @@ -1314,6 +1323,7 @@ int CmdTraceList(const char *Cmd) { "trace list -t hitag1 -> interpret as " _YELLOW_("Hitag 1") "\n" "trace list -t hitag2 -> interpret as " _YELLOW_("Hitag 2") "\n" "trace list -t hitags -> interpret as " _YELLOW_("Hitag S") "\n" + "trace list -t hitagu -> interpret as " _YELLOW_("Hitag µ") "\n" "trace list -t iclass -> interpret as " _YELLOW_("iCLASS") "\n" "trace list -t legic -> interpret as " _YELLOW_("LEGIC") "\n" "trace list -t lto -> interpret as " _YELLOW_("LTO-CM") "\n" @@ -1381,6 +1391,7 @@ int CmdTraceList(const char *Cmd) { else if (strcmp(type, "hitag1") == 0) protocol = PROTO_HITAG1; else if (strcmp(type, "hitag2") == 0) protocol = PROTO_HITAG2; else if (strcmp(type, "hitags") == 0) protocol = PROTO_HITAGS; + else if (strcmp(type, "hitagu") == 0) protocol = PROTO_HITAGU; else if (strcmp(type, "iclass") == 0) protocol = ICLASS; else if (strcmp(type, "legic") == 0) protocol = LEGIC; else if (strcmp(type, "lto") == 0) protocol = LTO; @@ -1470,8 +1481,8 @@ int CmdTraceList(const char *Cmd) { if (protocol == ISO_7816_4) PrintAndLogEx(INFO, _YELLOW_("ISO7816-4 / Smartcard") " - Timings n/a"); - if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) { - PrintAndLogEx(INFO, _YELLOW_("Hitag 1 / Hitag 2 / Hitag S") " - Timings in ETU (8us)"); + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS || protocol == PROTO_HITAGU) { + PrintAndLogEx(INFO, _YELLOW_("Hitag 1 / Hitag 2 / Hitag S / Hitag µ") " - Timings in ETU (8us)"); } if (protocol == PROTO_FMCOS20) { @@ -1552,7 +1563,7 @@ int CmdTraceList(const char *Cmd) { } // reset hitag state machine - if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS) { + if (protocol == PROTO_HITAG1 || protocol == PROTO_HITAG2 || protocol == PROTO_HITAGS || protocol == PROTO_HITAGU) { annotateHitag2_init(); } diff --git a/include/hitag.h b/include/hitag.h index 330ce8ddf..a8e64d452 100644 --- a/include/hitag.h +++ b/include/hitag.h @@ -13,7 +13,7 @@ // // See LICENSE.txt for the text of the license. //----------------------------------------------------------------------------- -// Hitag 2, Hitag S +// Hitag 2, Hitag S, Hitag µ //----------------------------------------------------------------------------- @@ -39,6 +39,26 @@ #define HITAGS_UID_PADR 0 #define HITAGS_CONFIG_PADR 1 +// Add Hitag µ specific definitions +#define HITAGU_UID_SIZE 6 +#define HITAGU_BLOCK_SIZE HITAG_BLOCK_SIZE +#define HITAGU_MAX_BLOCKS 0x100 +#define HITAGU_MAX_BYTE_SIZE (HITAGU_MAX_BLOCKS * HITAGU_BLOCK_SIZE) +#define HITAGU_CONFIG_PADR 0xFF +#define HITAGU_PASSWORD_PADR 0xFE + +// Hitag μ IC Revision (ICR) values +#define HITAGU_ICR_STANDARD 0x10 // Standard Hitag μ +#define HITAGU_ICR_ADVANCED 0x20 // Hitag μ advanced +#define HITAGU_ICR_ADVANCED_PLUS 0x30 // Hitag μ advanced+ +#define HITAGU_ICR_8265 0x80 // 8265 + +// Hitag μ memory sizes based on ICR +#define HITAGU_MAX_PAGE_STANDARD 0x04 // 4 blocks (0x00-0x03) for standard Hitag μ +#define HITAGU_MAX_PAGE_ADVANCED 0x10 // 16 blocks (0x00-0x0F) for Hitag μ advanced +#define HITAGU_MAX_PAGE_ADVANCED_PLUS 0x37 // 56 blocks (0x00-0x36) for Hitag μ advanced+ +#define HITAGU_MAX_PAGE_8265 0x0F // 15 blocks (0x00-0x0E) for 8265 + // need to see which limits these cards has #define HITAG1_MAX_BYTE_SIZE 64 #define HITAG_MAX_BYTE_SIZE (64 * HITAG_BLOCK_SIZE) @@ -58,18 +78,24 @@ typedef enum { HTSF_82xx, HTSF_CHALLENGE, HTSF_KEY, - HTS_LAST_CMD = HTSF_KEY, + HTS_LAST_CMD = HTSF_KEY, HT1F_PLAIN, HT1F_AUTHENTICATE, - HT1_LAST_CMD = HT1F_AUTHENTICATE, + HT1_LAST_CMD = HT1F_AUTHENTICATE, HT2F_PASSWORD, HT2F_AUTHENTICATE, HT2F_CRYPTO, HT2F_TEST_AUTH_ATTEMPTS, HT2F_UID_ONLY, - HT2_LAST_CMD = HT2F_UID_ONLY, + HT2_LAST_CMD = HT2F_UID_ONLY, + + // Add Hitag µ commands + HTUF_PLAIN, + HTUF_82xx, + HTUF_PASSWORD, + HTU_LAST_CMD = HTUF_PASSWORD, } PACKED hitag_function; //--------------------------------------------------------- @@ -150,6 +176,57 @@ struct hitagS_tag { } PACKED; +// Configuration byte 0 bit definitions +#define HITAGU_BYTE0_DATARATE_MASK 0x03 // Bits 0-1: data rate +#define HITAGU_BYTE0_DATARATE_2K 0x00 // 00 = 2kbit/s +#define HITAGU_BYTE0_DATARATE_4K 0x01 // 01 = 4kbit/s +#define HITAGU_BYTE0_DATARATE_8K 0x02 // 10 = 8kbit/s +#define HITAGU_BYTE0_ENCODING_MASK 0x04 // Bit 2: encoding +#define HITAGU_BYTE0_ENCODING_MANCHESTER 0x00 // 0 = Manchester +#define HITAGU_BYTE0_ENCODING_BIPHASE 0x01 // 1 = Biphase + +// Hitag µ configuration structure +typedef struct { + // byte0 + uint8_t datarate: 2; + uint8_t encoding: 1; + uint8_t pwdW0_127: 1; + uint8_t pwdW128_511: 1; + uint8_t pwdW512_max: 1; + uint8_t pwdRW512_max: 1; +} PACKED hitagu_config_t; + +typedef struct { + // byte0 + uint8_t datarate : 2; // 00 = 2kbit/s, 01 = 4kbit/s, 10 = 8kbit/s, 11 = 2kbit/s + uint8_t datarate_override : 1; // 0 = datarate, 1 = 2kbit/s + uint8_t encoding : 1; // 0 = Manchester, 1 = Biphase + + uint8_t reserved : 1; + uint8_t ttf_mode : 2; // 00/10/11 = "Block 0, Block 1, Block 2, Block 3", 01 = "Block 0, Block 1" + uint8_t ttf : 1; +} PACKED hitagu82xx_config_t; + +// Hitag µ tag structure +struct hitagU_tag { + PSTATE pstate; // protocol-state + TSATE tstate; // tag-state + + int max_page; + uint8_t uid[HITAGU_UID_SIZE]; + union { + uint8_t asBytes[HITAGU_BLOCK_SIZE]; + hitagu_config_t s; + hitagu82xx_config_t s82xx; + } config; + uint8_t password[HITAG_PASSWORD_SIZE]; + uint8_t icr; // IC Revision value - determines memory size + + union { + uint8_t pages[HITAGU_MAX_BLOCKS][HITAGU_BLOCK_SIZE]; + } data; +} PACKED; + typedef struct { hitag_function cmd; uint8_t page; @@ -170,6 +247,9 @@ typedef struct { // Hitag S section uint8_t mode; + + // Hitag µ section + uint8_t uid[HITAGU_UID_SIZE]; } PACKED lf_hitag_data_t; typedef struct { @@ -185,4 +265,18 @@ typedef struct { int8_t pages_reason[HITAGS_MAX_PAGES]; uint8_t pages[HITAGS_MAX_PAGES][HITAGS_PAGE_SIZE]; } PACKED lf_hts_read_response_t; + +// Hitag µ read response structure +typedef struct { + union { + uint8_t asBytes[HITAGU_BLOCK_SIZE]; + hitagu_config_t s; + hitagu82xx_config_t s82xx; + } config_page; + uint8_t uid[HITAGU_UID_SIZE]; + uint8_t icr; // IC Revision value for memory size detection + int8_t pages_reason[HITAGU_MAX_PAGE_ADVANCED_PLUS]; + uint8_t pages[HITAGU_MAX_PAGE_ADVANCED_PLUS][HITAGU_BLOCK_SIZE]; +} PACKED lf_htu_read_response_t; + #endif diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index a71e09a1e..d23f00870 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -282,7 +282,7 @@ typedef struct { typedef struct { // 64KB SRAM -> 524288 bits(max sample num) < 2^30 -uint32_t samples : + uint32_t samples : LF_SAMPLES_BITS; bool realtime : 1; bool verbose : 1; @@ -602,6 +602,12 @@ typedef struct { #define CMD_LF_HITAGS_WRITE 0x0375 #define CMD_LF_HITAGS_UID 0x037A +// For Hitag µ +#define CMD_LF_HITAGU_READ 0x037B +#define CMD_LF_HITAGU_WRITE 0x037C +#define CMD_LF_HITAGU_SIMULATE 0x037D +#define CMD_LF_HITAGU_UID 0x037E + #define CMD_LF_HITAG_ELOAD 0x0376 #define CMD_HF_ISO14443A_ANTIFUZZ 0x0380 diff --git a/include/protocols.h b/include/protocols.h index 7048263d5..70795fcd0 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -455,12 +455,14 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define LTO 12 #define PROTO_HITAG2 13 #define PROTO_HITAGS 14 -#define PROTO_CRYPTORF 15 -#define SEOS 16 -#define PROTO_MFPLUS 17 -#define PROTO_TEXKOM 18 -#define PROTO_XEROX 19 -#define PROTO_FMCOS20 20 +#define PROTO_HITAGU 15 +#define PROTO_CRYPTORF 16 +#define SEOS 17 +#define PROTO_MFPLUS 18 +#define PROTO_TEXKOM 19 +#define PROTO_XEROX 20 +#define PROTO_FMCOS20 21 +#define COUNT_OF_PROTOCOLS 22 // Picopass fuses #define FUSE_FPERS 0x80 @@ -948,6 +950,25 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define HITAGS_WRITE_BLOCK 0x90 // 1001 WRITE BLOCK #define HITAGS_QUIET 0x70 // 0111 QUIET +// Hitag μ flags +#define HITAGU_FLAG_PEXT 0x01 // 0b00001 - Protocol EXTension flag +#define HITAGU_FLAG_INV 0x02 // 0b00010 - INVentory flag +#define HITAGU_FLAG_CRCT 0x04 // 0b00100 - CRC Transponder flag +#define HITAGU_FLAG_SEL 0x08 // 0b01000 - SELect flag (when INV=0) +#define HITAGU_FLAG_ADR 0x10 // 0b10000 - ADdRess flag (when INV=0) +#define HITAGU_FLAG_RFU 0x08 // 0b01000 - Reserved For Use flag (when INV=1, always 0) +#define HITAGU_FLAG_NOS 0x10 // 0b10000 - Number Of Slots flag (when INV=1) + +// Hitag μ commands (6-bit) +#define HITAGU_CMD_LOGIN 0x28 // 0b101000 - Login command +#define HITAGU_CMD_INVENTORY 0x00 // 0b000000 - Inventory command +#define HITAGU_CMD_READ_MULTIPLE_BLOCK 0x12 // 0b010010 - Read multiple block command +#define HITAGU_CMD_WRITE_SINGLE_BLOCK 0x14 // 0b010100 - Write single block command +#define HITAGU_CMD_SELECT 0x18 // 0b011000 - Select command +#define HITAGU_CMD_SYSINFO 0x17 // 0b010111 - Get system information command +#define HITAGU_CMD_READ_UID 0x02 // 0b000010 - Read UID command +#define HITAGU_CMD_STAY_QUIET 0x01 // 0b000001 - Stay quiet command + // LTO-CM commands #define LTO_REQ_STANDARD 0x45 #define LTO_REQ_ALL 0x4A