mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-23 15:13:09 +00:00
753 lines
27 KiB
C
753 lines
27 KiB
C
//-----------------------------------------------------------------------------
|
|
// 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 <string.h> // strlen
|
|
|
|
#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 <ctype.h>
|
|
|
|
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;
|
|
|
|
snprintf(exp, size, "Flg:");
|
|
|
|
if ((flag & HITAGU_FLAG_PEXT) == HITAGU_FLAG_PEXT) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " PEXT");
|
|
}
|
|
|
|
if ((flag & HITAGU_FLAG_INV) == HITAGU_FLAG_INV) {
|
|
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " INV");
|
|
|
|
if ((flag & HITAGU_FLAG_RFU) == HITAGU_FLAG_RFU) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " RFU");
|
|
}
|
|
|
|
if ((flag & HITAGU_FLAG_NOS) == HITAGU_FLAG_NOS) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " NOS");
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((flag & HITAGU_FLAG_SEL) == HITAGU_FLAG_SEL) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " SEL");
|
|
}
|
|
|
|
if ((flag & HITAGU_FLAG_ADR) == HITAGU_FLAG_ADR) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " ADR");
|
|
has_uid = true;
|
|
}
|
|
}
|
|
|
|
if ((flag & HITAGU_FLAG_CRCT) == HITAGU_FLAG_CRCT) {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " CRCT");
|
|
}
|
|
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "|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))) {
|
|
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "8265 LOGIN");
|
|
|
|
} else if (cmdsize == (7 + (has_uid * HITAGU_UID_SIZE)) || cmdsize == (9 + (has_uid * HITAGU_UID_SIZE))) {
|
|
|
|
uint8_t mfc = 0;
|
|
concatbits(&mfc, 0, cmd, 5 + 6 + 8 + 32, 8, false);
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "LOGIN mfc:%02x ", mfc);
|
|
has_mfc = true;
|
|
}
|
|
|
|
if (has_uid) {
|
|
uint8_t uid[HITAGU_UID_SIZE] = {0};
|
|
concatbits(uid, 0, cmd, 5 + 6 + has_mfc * 8 + 32, HITAGU_UID_SIZE * 8, false);
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " uid:%s", sprint_hex_inrow(uid, HITAGU_UID_SIZE));
|
|
}
|
|
|
|
uint8_t password[HITAG_PASSWORD_SIZE] = {0};
|
|
concatbits(password, 0, cmd, 5 + 6 + has_mfc * 8 + has_uid * HITAGU_UID_SIZE * 8, HITAG_PASSWORD_SIZE * 8, false);
|
|
snprintf(exp + strlen(exp), size - strlen(exp), " pwd:%s", sprint_hex_inrow(password, HITAG_PASSWORD_SIZE));
|
|
break;
|
|
}
|
|
case HITAGU_CMD_INVENTORY: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "INVENTORY");
|
|
break;
|
|
}
|
|
case HITAGU_CMD_READ_MULTIPLE_BLOCK: {
|
|
uint8_t block_addr = 0;
|
|
concatbits(&block_addr, 0, cmd, 5 + 6, 8, false);
|
|
|
|
uint8_t block_count = 0;
|
|
concatbits(&block_count, 0, cmd, 5 + 6 + 8, 8, false);
|
|
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "READ MULTIPLE BLOCK start:%d num:%d"
|
|
, reflect8(block_addr)
|
|
, reflect8(block_count)
|
|
);
|
|
break;
|
|
}
|
|
case HITAGU_CMD_WRITE_SINGLE_BLOCK: {
|
|
uint8_t block_addr = 0;
|
|
concatbits(&block_addr, 0, cmd, 5 + 6, 8, false);
|
|
|
|
uint8_t block_data[4] = {0};
|
|
concatbits(block_data, 0, cmd, 5 + 6 + 8, 32, false);
|
|
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "WRITE SINGLE BLOCK start:%d data:[%s]"
|
|
, reflect8(block_addr)
|
|
, sprint_hex_inrow(block_data, 4)
|
|
);
|
|
break;
|
|
}
|
|
case HITAGU_CMD_SELECT: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "SELECT");
|
|
break;
|
|
}
|
|
case HITAGU_CMD_SYSINFO: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "GET SYSTEM INFORMATION");
|
|
break;
|
|
}
|
|
case HITAGU_CMD_READ_UID: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "READ UID");
|
|
break;
|
|
}
|
|
case HITAGU_CMD_STAY_QUIET: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "STAY QUIET");
|
|
break;
|
|
}
|
|
default: {
|
|
snprintf(exp + strlen(exp), size - strlen(exp), "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) {
|
|
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) {
|
|
|
|
//todo: USE ENUM OR DEFINES
|
|
switch (reason) {
|
|
case 0: {
|
|
PrintAndLogEx(INFO, "No data");
|
|
break;
|
|
}
|
|
case -2: {
|
|
PrintAndLogEx(FAILED, "READ UID failed!");
|
|
break;
|
|
}
|
|
case -3: {
|
|
PrintAndLogEx(FAILED, "Get System Information / Config failed!");
|
|
break;
|
|
}
|
|
case -4: {
|
|
PrintAndLogEx(FAILED, "Login failed! Wrong password?");
|
|
break;
|
|
}
|
|
case -5: {
|
|
PrintAndLogEx(FAILED, "No write access on block. Not authorized?");
|
|
break;
|
|
}
|
|
case -6: {
|
|
PrintAndLogEx(FAILED, "Response CRC invalid!");
|
|
break;
|
|
}
|
|
case -7: {
|
|
PrintAndLogEx(FAILED, "Read block failed!");
|
|
break;
|
|
}
|
|
default: {
|
|
// PM3_REASON_UNKNOWN
|
|
PrintAndLogEx(FAILED, "Error - Hitag µ failed");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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", "<hex>", "pwd, 4 hex bytes"),
|
|
arg_int0("p", "page", "<dec>", "block address to read from (def: 0)"),
|
|
arg_int0("c", "count", "<dec>", "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) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint32_t page = arg_get_int_def(ctx, 3, 0);
|
|
|
|
if (page >= HITAGU_MAX_BLOCKS) {
|
|
PrintAndLogEx(WARNING, "Block address out-of-range. Max is 255, got %u", page);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint32_t count = arg_get_int_def(ctx, 4, 1);
|
|
|
|
if (count > HITAGU_MAX_BLOCKS) {
|
|
PrintAndLogEx(WARNING, "No more than %d blocks can be read at once", HITAGU_MAX_BLOCKS);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
packet.page = page;
|
|
packet.page_count = count;
|
|
// packet.mode = 1; // for debug
|
|
|
|
PrintAndLogEx(INFO, "Read Hitag µ memory block " _YELLOW_("%d") ", count " _YELLOW_("%d"), page, count);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_LF_HITAGU_READ, (uint8_t *)&packet, sizeof(packet));
|
|
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_LF_HITAGU_READ, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
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;
|
|
}
|
|
|
|
// A Hitag u reports its UID and IC revision separately from its pages, so
|
|
// neither survives in the raw dump buffer. saveFileJSON's callback takes only
|
|
// the json root, so park them here for the duration of the save.
|
|
static uint8_t s_htu_uid[HITAGU_UID_SIZE] = {0};
|
|
static uint8_t s_htu_icr = 0;
|
|
|
|
static void htu_json_callback(json_t *root) {
|
|
JsonSaveBufAsHexCompact(root, "$.Card.UID", s_htu_uid, sizeof(s_htu_uid));
|
|
JsonSaveBufAsHexCompact(root, "$.Card.ICR", &s_htu_icr, sizeof(s_htu_icr));
|
|
}
|
|
|
|
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", "<hex>", "pwd, 4 hex bytes"),
|
|
arg_str0("f", "file", "<fn>", "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-htu-");
|
|
FillFileNameByUID(fptr, card->uid, "-dump", HITAGU_UID_SIZE);
|
|
}
|
|
|
|
memcpy(s_htu_uid, card->uid, sizeof(s_htu_uid));
|
|
s_htu_icr = card->icr;
|
|
pm3_save_dump_cb(filename, (uint8_t *)card->pages, mem_size, jsfHitagU, htu_json_callback);
|
|
|
|
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", "<hex>", "pwd, 4 hex bytes"),
|
|
arg_int1("p", "page", "<dec>", "block address to write to"),
|
|
arg_str1("d", "data", "<hex>", "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) {
|
|
CLIParserFree(ctx);
|
|
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_("<Enter>") " to exit");
|
|
}
|
|
|
|
do {
|
|
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", "<dec>", "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();
|
|
hitag_sim_t payload = {
|
|
.tag_mem_supplied = false,
|
|
.threshold = threshold,
|
|
};
|
|
SendCommandNG(CMD_LF_HITAGU_SIMULATE, (uint8_t *)&payload, sizeof(payload));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdLFHitagUList(const char *Cmd) { return CmdTraceListAlias(Cmd, "lf hitag htu", "htu"); }
|
|
|
|
static command_t CommandTable[] = {
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"list", CmdLFHitagUList, AlwaysAvailable, "List Hitag µ trace history"},
|
|
{"-----------", CmdHelp, IfPm3Hitag, "----------- " _CYAN_("General") " -----------"},
|
|
{"reader", CmdLFHitagUReader, IfPm3Hitag, "Act like a Hitag µ reader"},
|
|
{"rdbl", CmdLFHitagURead, IfPm3Hitag, "Read Hitag µ block"},
|
|
{"dump", CmdLFHitagUDump, IfPm3Hitag, "Dump Hitag µ blocks to a file"},
|
|
{"wrbl", CmdLFHitagUWrite, IfPm3Hitag, "Write Hitag µ block"},
|
|
{"-----------", CmdHelp, IfPm3Hitag, "----------- " _CYAN_("Simulation") " -----------"},
|
|
{"sim", CmdLFHitagUSim, IfPm3Hitag, "Simulate Hitag µ transponder"},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
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);
|
|
}
|