From 62980b50388dbb0d91899822ac06162936710b07 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 5 Aug 2022 22:46:59 +0200 Subject: [PATCH] refactored topaz commands. It still uses a global var with dynamic memory. Added rdbl, wrbl, view, dump commands to match rest of client --- CHANGELOG.md | 4 +- client/src/cmdhfmfu.c | 16 +- client/src/cmdhftopaz.c | 578 ++++++++++++++++++++++++++++++++-------- client/src/cmdhftopaz.h | 26 +- client/src/fileutils.c | 51 ++++ client/src/fileutils.h | 1 + client/src/nfc/ndef.c | 30 ++- 7 files changed, 567 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f074cc6d..8f87454e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,14 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added json topaz file format (@iceman1001) + - Added `hf topaz rdbl, wrbl, view` commands (@iceman1001) - Added more details to the annotations of `hf mfdes list` output (@nvx) - Changed `hf iclass view` and related to suppress consecutive blocks with repeated contents with a `-z` flag, or `prefs set output --dense` (@nvx) - Changed `hf iclass list` to display matched keys on the CHECK command rather than the card response, and made it check for elite keys too (@nvx) - Fixed `hf iclass info` and `hf iclass view` key access info looking at the wrong card config bit (@nvx) - Added `hf gallagher decode` command and fix Gallagher diversification for card master key (@nvx) - - Added mmbit-002 (kibi-002, kb5004xk1) russian tag to `hf texkom read` command (@merlokk) + - Changed `hf texkom reader` - now supports mmbit-002 (kibi-002, kb5004xk1) russian tag (@merlokk) - Added `hf sniff --smode` skip/group adc data to consume less memory. Now it can sniff very long signals (@merlokk) - Added `hf fudan` skeleton commands (@iceman1001) - Added `--reboot-to-bootloader` arg to pm3 diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 60196afbd..7ff381d22 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -606,14 +606,13 @@ static int ndef_print_CC(uint8_t *data) { uint8_t mlrule = (data[3] & 0x06) >> 1; uint8_t mbread = (data[3] & 0x01); - PrintAndLogEx(SUCCESS, " Additional feature information"); - PrintAndLogEx(SUCCESS, " %02X", data[3]); + PrintAndLogEx(SUCCESS, " %02X: Additional feature information", data[3]); PrintAndLogEx(SUCCESS, " 00000000"); - PrintAndLogEx(SUCCESS, " xxx - %02X: RFU ( %s )", msb3, (msb3 == 0) ? _GREEN_("ok") : _RED_("fail")); - PrintAndLogEx(SUCCESS, " x - %02X: %s special frame", sf, (sf) ? "support" : "don\'t support"); - PrintAndLogEx(SUCCESS, " x - %02X: %s lock block", lb, (lb) ? "support" : "don\'t support"); - PrintAndLogEx(SUCCESS, " xx - %02X: RFU ( %s )", mlrule, (mlrule == 0) ? _GREEN_("ok") : _RED_("fail")); - PrintAndLogEx(SUCCESS, " x - %02X: IC %s multiple block reads", mbread, (mbread) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS, " xxx..... - %02X: RFU ( %s )", msb3, (msb3 == 0) ? _GREEN_("ok") : _RED_("fail")); + PrintAndLogEx(SUCCESS, " ...x.... - %02X: %s special frame", sf, (sf) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS, " ....x... - %02X: %s lock block", lb, (lb) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS, " .....xx. - %02X: RFU ( %s )", mlrule, (mlrule == 0) ? _GREEN_("ok") : _RED_("fail")); + PrintAndLogEx(SUCCESS, " .......x - %02X: IC %s multiple block reads", mbread, (mbread) ? "support" : "don\'t support"); return PM3_SUCCESS; } @@ -3243,7 +3242,8 @@ static int CmdHF14AMfUPwdGen(const char *Cmd) { } PrintAndLogEx(INFO, "------------------.---------------"); - PrintAndLogEx(INFO, " Using UID : " _YELLOW_("%s"), sprint_hex(uid, 7)); + PrintAndLogEx(INFO, " Using UID 4b: " _YELLOW_("%s"), sprint_hex(uid, 4)); + PrintAndLogEx(INFO, " Using UID 7b: " _YELLOW_("%s"), sprint_hex(uid, 7)); PrintAndLogEx(INFO, "----------------------------------"); PrintAndLogEx(INFO, " algo | pwd | pack"); PrintAndLogEx(INFO, "-----------------+----------+-----"); diff --git a/client/src/cmdhftopaz.c b/client/src/cmdhftopaz.c index 12a445408..6d7a1975d 100644 --- a/client/src/cmdhftopaz.c +++ b/client/src/cmdhftopaz.c @@ -38,25 +38,7 @@ # define AddCrc14B(data, len) compute_crc(CRC_14443_B, (data), (len), (data)+(len), (data)+(len)+1) #endif -#define TOPAZ_STATIC_MEMORY (0x0F * 8) // 15 blocks with 8 Bytes each - -// a struct to describe a memory area which contains lock bits and the corresponding lockable memory area -typedef struct dynamic_lock_area { - struct dynamic_lock_area *next; - uint16_t byte_offset; // the address of the lock bits - uint16_t size_in_bits; - uint16_t first_locked_byte; // the address of the lockable area - uint16_t bytes_locked_per_bit; -} dynamic_lock_area_t; - -static struct { - uint8_t HR01[2]; - uint8_t uid[7]; - uint16_t size; - uint8_t data_blocks[TOPAZ_STATIC_MEMORY / 8][8]; // this memory is always there - uint8_t *dynamic_memory; // this memory can be there - dynamic_lock_area_t *dynamic_lock_areas; // lock area descriptors -} topaz_tag; +static topaz_tag_t topaz_tag; static void topaz_switch_on_field(void) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_SELECT | ISO14A_NO_DISCONNECT | ISO14A_TOPAZMODE | ISO14A_NO_RATS, 0, 0, NULL, 0); @@ -142,30 +124,68 @@ static int topaz_rall(uint8_t *uid, uint8_t *response) { } // read a block (8 Bytes) of a selected Topaz tag. -static int topaz_read_block(uint8_t *uid, uint8_t blockno, uint8_t *block_data) { - uint16_t resp_len = 0; - uint8_t read8_cmd[] = {TOPAZ_READ8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t read8_response[11] = {0}; +static int topaz_read_block(uint8_t blockno, uint8_t *block_data) { + uint8_t atqa[2] = {0}; + uint8_t rid_response[8] = {0}; + int res = topaz_select(atqa, sizeof(atqa), rid_response, sizeof(rid_response), true); + if (res != PM3_SUCCESS) { + return res; + } - read8_cmd[1] = blockno; - memcpy(&read8_cmd[10], uid, 4); + if (atqa[1] != 0x0c && atqa[0] != 0x00) { + return res; + } - if (topaz_send_cmd(read8_cmd, sizeof(read8_cmd), read8_response, &resp_len, true) == PM3_ETIMEOUT) { + uint8_t *uid_echo = &rid_response[2]; + uint8_t rall_response[124] = {0}; + + res = topaz_rall(uid_echo, rall_response); + if (res == PM3_ESOFT) { + return res; + } + + uint8_t read8_cmd[] = {TOPAZ_READ8, blockno, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(&read8_cmd[10], uid_echo, 4); + + uint16_t resp_len = 11; + uint8_t response[11] = {0}; + + if (topaz_send_cmd(read8_cmd, sizeof(read8_cmd), response, &resp_len, true) == PM3_ETIMEOUT) { topaz_switch_off_field(); return PM3_ESOFT; // READ8 failed } - memcpy(block_data, &read8_response[1], 8); + memcpy(block_data, &response[1], 8); return PM3_SUCCESS; } // read a segment (16 blocks = 128 Bytes) of a selected Topaz tag. Works only for tags with dynamic memory. -static int topaz_read_segment(uint8_t *uid, uint8_t segno, uint8_t *segment_data) { - uint16_t resp_len = 0; +static int topaz_read_segment(uint8_t segno, uint8_t *segment_data) { + + uint8_t atqa[2] = {0}; + uint8_t rid_response[8] = {0}; + int res = topaz_select(atqa, sizeof(atqa), rid_response, sizeof(rid_response), true); + if (res != PM3_SUCCESS) { + return res; + } + + if (atqa[1] != 0x0c && atqa[0] != 0x00) { + return res; + } + + uint8_t *uid_echo = &rid_response[2]; + uint8_t rall_response[124] = {0}; + + res = topaz_rall(uid_echo, rall_response); + if (res == PM3_ESOFT) { + return res; + } + + uint16_t resp_len = 131; uint8_t rseg_cmd[] = {TOPAZ_RSEG, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint8_t rseg_response[131]; rseg_cmd[1] = segno << 4; - memcpy(&rseg_cmd[10], uid, 4); + memcpy(&rseg_cmd[10], uid_echo, 4); if (topaz_send_cmd(rseg_cmd, sizeof(rseg_cmd), rseg_response, &resp_len, true) == PM3_ETIMEOUT) { topaz_switch_off_field(); @@ -175,6 +195,55 @@ static int topaz_read_segment(uint8_t *uid, uint8_t segno, uint8_t *segment_data return PM3_SUCCESS; } +// write a block (8 Bytes) of a selected Topaz tag. +static int topaz_write_erase8_block(uint8_t blockno, uint8_t *block_data) { + + uint8_t atqa[2] = {0}; + uint8_t rid_response[8] = {0}; + int res = topaz_select(atqa, sizeof(atqa), rid_response, sizeof(rid_response), true); + if (res != PM3_SUCCESS) { + return res; + } + + if (atqa[1] != 0x0c && atqa[0] != 0x00) { + return res; + } + + uint8_t *uid_echo = &rid_response[2]; + uint8_t rall_response[124] = {0}; + + res = topaz_rall(uid_echo, rall_response); + if (res == PM3_ESOFT) { + return res; + } + + uint8_t wr8_cmd[] = {TOPAZ_WRITE_E8, blockno, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(wr8_cmd + 10, uid_echo, 4); + memcpy(wr8_cmd + 2, block_data, 8); + + uint16_t resp_len = 11; + uint8_t response[11] = {0}; + + if (topaz_send_cmd(wr8_cmd, sizeof(wr8_cmd), response, &resp_len, true) == PM3_ETIMEOUT) { + topaz_switch_off_field(); + return PM3_ESOFT; // WriteErase 8bytes failed + } + + if (resp_len != 11) { + return PM3_EFAILED; + } + + if (blockno != response[0]) { + return PM3_EFAILED; + } + + if (memcmp(block_data, response + 1, 8) == 0) { + return PM3_SUCCESS; + } + return PM3_ESOFT; +} + + // search for the lock area descriptor for the lockable area including byteno static dynamic_lock_area_t *get_dynamic_lock_area(uint16_t byteno) { dynamic_lock_area_t *lock_area; @@ -223,25 +292,112 @@ static bool topaz_byte_is_locked(uint16_t byteno) { } } -// read and print the Capability Container -static int topaz_print_CC(uint8_t *data) { - if (data[0] != 0xe1) { +static int topaz_set_cc_dynamic(uint8_t *data) { + + if (data[0] != 0xE1) { topaz_tag.size = TOPAZ_STATIC_MEMORY; + PrintAndLogEx(WARNING, "No Type 1 NDEF capability container found"); return PM3_ESOFT; // no NDEF message } - PrintAndLogEx(SUCCESS, "Capability Container: %02x %02x %02x %02x", data[0], data[1], data[2], data[3]); - PrintAndLogEx(SUCCESS, " %02x: NDEF Magic Number", data[0]); - PrintAndLogEx(SUCCESS, " %02x: version %d.%d supported by tag", data[1], (data[1] & 0xF0) >> 4, data[1] & 0x0f); - + // setting of dynamic memory and allocation of such memory. uint16_t memsize = (data[2] + 1) * 8; topaz_tag.size = memsize; topaz_tag.dynamic_memory = calloc(memsize - TOPAZ_STATIC_MEMORY, sizeof(uint8_t)); + if (topaz_tag.dynamic_memory == NULL) { + return PM3_EMALLOC; + } + return PM3_SUCCESS; +} - PrintAndLogEx(SUCCESS, " %02x: Physical Memory Size of this tag: %d bytes", data[2], memsize); - PrintAndLogEx(SUCCESS, " %02x: %s / %s", data[3], - (data[3] & 0xF0) ? "(RFU)" : "Read access granted without any security", - (data[3] & 0x0F) == 0 ? "Write access granted without any security" : (data[3] & 0x0F) == 0x0F ? "No write access granted at all" : "(RFU)"); +// read and print the Capability Container +static int topaz_print_CC(uint8_t *data) { + + if (data[0] != 0xE1) { + topaz_tag.size = TOPAZ_STATIC_MEMORY; + PrintAndLogEx(WARNING, "No Type 1 NDEF capability container found"); + return PM3_ESOFT; // no NDEF message + } + + +//NFC Forum Type 1,2,3,4 +// +// 4 has 1.1 (11) + +// b7, b6 major version +// b5, b4 minor version +// b3, b2 read +// 00 always, 01 rfu, 10 proprietary, 11 rfu +// b1, b0 write +// 00 always, 01 rfo, 10 proprietary, 11 never + +// vs + +// NFC Forum Type 2 docs, where +// b7654 = major version +// b3219 = minor version + +// CC write / read access +// (data[3] & 0xF0) ? "(RFU)" : "Read access granted without any security", +// (data[3] & 0x0F) == 0 ? "Write access granted without any security" : (data[3] & 0x0F) == 0x0F ? "No write access granted at all" : "(RFU)"); + uint8_t cc_major = (data[1] & 0xF0) >> 4; + uint8_t cc_minor = (data[1] & 0x00); + + uint8_t cc_write = data[3] & 0x0F; + uint8_t cc_read = (data[3] & 0xF0) >> 4; + + const char *wStr; + switch (cc_write) { + case 0: + wStr = "Write access granted without any security"; + break; + case 0xF: + wStr = "No write access"; + break; + default: + wStr = "RFU"; + break; + } + const char *rStr; + switch (cc_read) { + case 0: + rStr = "Read access granted without any security"; + break; + default: + rStr = "RFU"; + break; + } + + PrintAndLogEx(SUCCESS, "Capability Container: %s", sprint_hex(data, 4)); + PrintAndLogEx(SUCCESS, " %02X: NDEF Magic Number", data[0]); + +// PrintAndLogEx(SUCCESS, " %02X : version %d.%d supported by tag", data[1], (data[1] & 0xF0) >> 4, data[1] & 0x0F); + PrintAndLogEx(SUCCESS, " %02X: version %d.%d supported by tag", data[1], cc_major, cc_minor); + PrintAndLogEx(SUCCESS, " : %s / %s", rStr, wStr); + + PrintAndLogEx(SUCCESS, " %02X: Physical Memory Size: %d bytes", data[2], (data[2] + 1) * 8); + if (data[2] == 0x0E) + PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 120); + else if (data[2] == 0x1F) + PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 256); + else if (data[2] == 0xFF) + PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 2048); + + uint8_t msb3 = (data[3] & 0xE0) >> 5; + uint8_t sf = (data[3] & 0x10) >> 4; + uint8_t lb = (data[3] & 0x08) >> 3; + uint8_t mlrule = (data[3] & 0x06) >> 1; + uint8_t mbread = (data[3] & 0x01); + + PrintAndLogEx(SUCCESS, " %02X: Additional feature information", data[3]); + PrintAndLogEx(SUCCESS, " ^^"); + PrintAndLogEx(SUCCESS, " 00000000"); + PrintAndLogEx(SUCCESS, " xxx..... - %02X: RFU ( %s )", msb3, (msb3 == 0) ? _GREEN_("ok") : _RED_("fail")); + PrintAndLogEx(SUCCESS, " ...x.... - %02X: %s special frame", sf, (sf) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS, " ....x... - %02X: %s lock block", lb, (lb) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS, " .....xx. - %02X: RFU ( %s )", mlrule, (mlrule == 0) ? _GREEN_("ok") : _RED_("fail")); + PrintAndLogEx(SUCCESS, " .......x - %02X: IC %s multiple block reads", mbread, (mbread) ? "support" : "don\'t support"); + PrintAndLogEx(SUCCESS,""); return PM3_SUCCESS; } @@ -314,10 +470,8 @@ static void topaz_print_control_TLVs(uint8_t *memory) { uint16_t bytes_locked_per_bit = 1 << (TLV_value[2] >> 4); uint16_t area_start = pages_addr * bytes_per_page + byte_offset; - PrintAndLogEx(SUCCESS, "Lock Area of %d bits at byte offset 0x%04x. Each Lock Bit locks %d bytes.", - size_in_bits, - area_start, - bytes_locked_per_bit); + PrintAndLogEx(SUCCESS, "Lock Area of " _YELLOW_("%d") " bits at byte offset " _YELLOW_("0x%04x"), size_in_bits, area_start); + PrintAndLogEx(SUCCESS, "Each lock bit locks " _YELLOW_("%d") " bytes", bytes_locked_per_bit); lock_TLV_present = true; dynamic_lock_area_t *old = topaz_tag.dynamic_lock_areas; @@ -352,7 +506,7 @@ static void topaz_print_control_TLVs(uint8_t *memory) { uint8_t bytes_per_page = 1 << (TLV_value[2] & 0x0f); uint16_t area_start = pages_addr * bytes_per_page + byte_offset; - PrintAndLogEx(SUCCESS, "Reserved Memory of %d bytes at byte offset 0x%02x.", + PrintAndLogEx(SUCCESS, "Reserved Memory... " _GREEN_("%d") " bytes at byte offset " _YELLOW_("0x%02x"), size_in_bytes, area_start); @@ -378,15 +532,15 @@ static void topaz_print_control_TLVs(uint8_t *memory) { // read all of the dynamic memory static int topaz_read_dynamic_data(void) { // first read the remaining block of segment 0 - if (topaz_read_block(topaz_tag.uid, 0x0F, &topaz_tag.dynamic_memory[0]) == PM3_ESOFT) { + if (topaz_read_block(0x0F, &topaz_tag.dynamic_memory[0]) == PM3_ESOFT) { PrintAndLogEx(ERR, "Error while reading dynamic memory block " _YELLOW_("%02x") ". Aborting...", 0x0F); return PM3_ESOFT; } // read the remaining segments - uint8_t max_segment = topaz_tag.size / 128 - 1; - for (uint8_t segment = 1; segment <= max_segment; segment++) { - if (topaz_read_segment(topaz_tag.uid, segment, &topaz_tag.dynamic_memory[(segment - 1) * 128 + 8]) == PM3_ESOFT) { + uint8_t max = topaz_tag.size / 128 - 1; + for (uint8_t segment = 1; segment <= max; segment++) { + if (topaz_read_segment(segment, &topaz_tag.dynamic_memory[(segment - 1) * 128 + 8]) == PM3_ESOFT) { PrintAndLogEx(ERR, "Error while reading dynamic memory block " _YELLOW_("%02x") ". Aborting...", segment); return PM3_ESOFT; } @@ -403,10 +557,10 @@ static void topaz_print_dynamic_data(void) { PrintAndLogEx(SUCCESS, "Dynamic Data blocks:"); - if (topaz_read_dynamic_data() == 0) { + if (topaz_read_dynamic_data() == PM3_SUCCESS) { - PrintAndLogEx(NORMAL, "block# | offset | Data | Locked (y/n)"); - PrintAndLogEx(NORMAL, "-------+--------+-------------------------+-------------"); + PrintAndLogEx(SUCCESS, "block# | Data |lck"); + PrintAndLogEx(SUCCESS, "-------+-------------------------+-------------"); char line[80]; for (uint16_t blockno = 0x0F; blockno < topaz_tag.size / 8; blockno++) { @@ -420,7 +574,7 @@ static void topaz_print_dynamic_data(void) { } lockbits[8] = '\0'; - PrintAndLogEx(NORMAL, " 0x%02x | 0x%04x | %s| %-3s", blockno, blockno * 8, line, lockbits); + PrintAndLogEx(SUCCESS, " 0x%02x | %s| %-3s", blockno, line, lockbits); } } } @@ -429,6 +583,43 @@ static void topaz_print_lifecycle_state(uint8_t *data) { // to be done } +static void printTopazDumpContents(uint8_t *dump, size_t size) { + + topaz_tag_t *t = (topaz_tag_t *)dump; + + // uses a global var for all + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Static data blocks :"); + PrintAndLogEx(SUCCESS, "block# | data |lck| info"); + PrintAndLogEx(SUCCESS, "------------+-------------------------+---+------------"); + + const char *block_info; + const char *topaz_ks[] = { "uid", "user", "rfu", "lock / otp" }; + + for (uint8_t i = 0; i <= 0x0C; i++) { + + if (i == 0) + block_info = topaz_ks[i]; + else + block_info = topaz_ks[1]; + + const char *lockstr = (topaz_byte_is_locked(i * 8)) ? _RED_("x") : " "; + + PrintAndLogEx(SUCCESS, " %3u / 0x%02x | %s| %s | %s", + i, + i, + sprint_hex(&t->data_blocks[i][0], 8), + lockstr, + block_info + ); + } + + PrintAndLogEx(SUCCESS, " %3u / 0x%02x | %s| | %s", 0x0D, 0x0D, sprint_hex(&t->data_blocks[0x0D][0], 8), topaz_ks[2]); + PrintAndLogEx(SUCCESS, " %3u / 0x%02x | %s| | %s", 0x0E, 0x0E, sprint_hex(&t->data_blocks[0x0E][0], 8), topaz_ks[3]); + PrintAndLogEx(SUCCESS, "------------+-------------------------+---+------------"); + PrintAndLogEx(NORMAL, ""); +} + static int CmdHFTopazReader(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf topaz reader", @@ -497,53 +688,21 @@ int CmdHFTopazInfo(const char *Cmd) { PrintAndLogEx(SUCCESS, " ^^"); PrintAndLogEx(SUCCESS, " %s", sprint_bin(topaz_tag.HR01, 1)); - PrintAndLogEx(SUCCESS, " x.......... TOPAZ tag ( %s ) - NDEF capable ( %s )", - (topaz_tag.HR01[0] & 0xF0) == 0x10 ? _GREEN_("yes") : _RED_("no"), - (topaz_tag.HR01[0] & 0xF0) == 0x10 ? _GREEN_("yes") : _RED_("no") + PrintAndLogEx(SUCCESS, " ...x.... - %s / %s", + (topaz_tag.HR01[0] & 0xF0) == 0x10 ? _GREEN_("TOPAZ tag") : "", + (topaz_tag.HR01[0] & 0xF0) == 0x10 ? _GREEN_("Type 1 NDEF") : "" ); - PrintAndLogEx(SUCCESS, " x... %s memory map", ((topaz_tag.HR01[0] & 0x0F) == 0x01) ? "Static" : "Dynamic" ); - PrintAndLogEx(NORMAL, ""); - - PrintAndLogEx(SUCCESS, "Static Data blocks " _YELLOW_("0x00") " to " _YELLOW_("0x0C")":"); - PrintAndLogEx(NORMAL, "block# | offset | Data | Locked"); - PrintAndLogEx(NORMAL, "-------+--------+-------------------------+------------"); - - char line[80]; - for (uint8_t i = 0; i <= 0x0C; i++) { - - char lockbits[9]; - for (uint8_t j = 0; j < 8; j++) { - int offset = 3 * j; - snprintf(line + offset, sizeof(line) - offset, "%02x ", topaz_tag.data_blocks[i][j] /*rall_response[2 + 8*i + j]*/); - lockbits[j] = topaz_byte_is_locked(i * 8 + j) ? 'y' : 'n'; - } - lockbits[8] = '\0'; - PrintAndLogEx(NORMAL, " 0x%02x | 0x%02x | %s| %-3s", i, i * 8, line, lockbits); - } - - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "Static Reserved block " _YELLOW_("0x0D")":"); - - for (uint8_t j = 0; j < 8; j++) { - int offset = 3 * j; - snprintf(line + offset, sizeof(line) - offset, "%02x ", topaz_tag.data_blocks[0x0d][j]); - } - - PrintAndLogEx(NORMAL, "-------+--------+-------------------------+------------"); - PrintAndLogEx(NORMAL, " 0x%02x | 0x%02x | %s| %-3s", 0x0d, 0x0d * 8, line, "n/a"); - PrintAndLogEx(NORMAL, ""); - - PrintAndLogEx(SUCCESS, "Static Lockbits and OTP Bytes:"); - - for (uint8_t j = 0; j < 8; j++) { - int offset = 3 * j; - snprintf(line + offset, sizeof(line) - offset, "%02x ", topaz_tag.data_blocks[0x0e][j]); - } - - PrintAndLogEx(NORMAL, "-------+--------+-------------------------+------------"); - PrintAndLogEx(NORMAL, " 0x%02x | 0x%02x | %s| %-3s", 0x0e, 0x0e * 8, line, "n/a"); + PrintAndLogEx(SUCCESS, " .......x - %s memory map", ((topaz_tag.HR01[0] & 0x0F) == 0x01) ? "Static" : "Dynamic" ); + PrintAndLogEx(SUCCESS, ""); + PrintAndLogEx(SUCCESS, " Lock bytes... %02X%02X", + topaz_tag.data_blocks[0x0e][0], + topaz_tag.data_blocks[0x0e][1] + ); + + PrintAndLogEx(SUCCESS, " OTP.......... %s", sprint_hex(&topaz_tag.data_blocks[0x0e][2], 6)); PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("NDEF configuration") " ---------------------------"); status = topaz_print_CC(&topaz_tag.data_blocks[1][0]); if (status == PM3_ESOFT) { PrintAndLogEx(SUCCESS, "No NDEF message data present"); @@ -555,16 +714,6 @@ int CmdHFTopazInfo(const char *Cmd) { topaz_print_control_TLVs(&topaz_tag.data_blocks[1][4]); PrintAndLogEx(NORMAL, ""); - topaz_print_dynamic_data(); - - topaz_print_lifecycle_state(&topaz_tag.data_blocks[1][0]); - - if (fnlen != 0) { - saveFile(filename, ".bin", &topaz_tag.data_blocks[1][0], TOPAZ_STATIC_MEMORY); - } - NDEFDecodeAndPrint(&topaz_tag.data_blocks[1][0], TOPAZ_STATIC_MEMORY, true); - - PrintAndLogEx(INFO, "-------------------------------------------------------------"); topaz_switch_off_field(); return PM3_SUCCESS; } @@ -585,7 +734,7 @@ static int CmdHFTopazSim(const char *Cmd) { return PM3_SUCCESS; } -static int CmdHFTopazCmdRaw(const char *Cmd) { +static int CmdHFTopazRaw(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf topaz raw", "Send raw hex data to Topaz tags", @@ -624,16 +773,213 @@ static int CmdHFTopazSniff(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHFTopazDump(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz dump", + "Dump TOPAZ tag to binary file\n" + "If no given, UID will be used as filename", + "hf topaz dump\n"); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "file", "", "filename of dump"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + int status = readTopazUid(false, false); + if (status != PM3_SUCCESS) { + return status; + } + printTopazDumpContents((uint8_t*)&topaz_tag, sizeof(topaz_tag_t)); + + bool set_dynamic = false; + if (topaz_set_cc_dynamic(&topaz_tag.data_blocks[1][0]) == PM3_SUCCESS) { + set_dynamic = true; + + topaz_print_dynamic_data(); + + topaz_print_lifecycle_state(&topaz_tag.data_blocks[1][0]); + + NDEFDecodeAndPrint(&topaz_tag.data_blocks[1][4], + (topaz_tag.HR01[0] == 1) ? (12 * 8) : 476 + , true + ); + } + + PrintAndLogEx(INFO, "-------------------------------------------------------------"); + topaz_switch_off_field(); + + // user supplied filename? + if (fnlen < 1) { + PrintAndLogEx(INFO, "Using UID as filename"); + strcat(filename, "hf-topaz-"); + FillFileNameByUID(filename, topaz_tag.uid, "-dump", sizeof(topaz_tag.uid)); + } + + if (topaz_tag.size) + pm3_save_dump(filename, (uint8_t *)&topaz_tag, sizeof(topaz_tag_t) + topaz_tag.size, jsfTopaz, TOPAZ_BLOCK_SIZE); + else + pm3_save_dump(filename, (uint8_t *)&topaz_tag, sizeof(topaz_tag_t), jsfTopaz, TOPAZ_BLOCK_SIZE); + + if (set_dynamic) { + free(topaz_tag.dynamic_memory); + } + + return PM3_SUCCESS; +} + +static int CmdHFTopazView(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz view", + "Print a Topaz tag dump file (bin/eml/json)", + "hf topaz view -f hf-topaz-04010203-dump.bin"); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "filename of dump (bin/eml/json)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + int fnlen = 0; + char filename[FILE_PATH_SIZE]; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + // read dump file + uint8_t *dump = NULL; + size_t bytes_read = TOPAZ_MAX_SIZE; + int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, sizeof(topaz_tag_t) + TOPAZ_MAX_SIZE); + if (res != PM3_SUCCESS) { + return res; + } + + printTopazDumpContents(dump, bytes_read); + + if (topaz_set_cc_dynamic(&topaz_tag.data_blocks[1][0]) == PM3_SUCCESS) { + + topaz_print_dynamic_data(); + + topaz_print_lifecycle_state(&topaz_tag.data_blocks[1][0]); + + NDEFDecodeAndPrint(&topaz_tag.data_blocks[1][4], + (topaz_tag.HR01[0] == 1) ? (12 * 8) : 476 + , true + ); + + PrintAndLogEx(INFO, "%s", sprint_hex(&topaz_tag.data_blocks[1][4], (12*8))); + + free(topaz_tag.dynamic_memory); + } + + free(dump); + return PM3_SUCCESS; +} + +// Read single block +static int CmdHFTopazRdBl(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz rdbl", + "Read a block", + "hf topaz rdbl -b 7\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "Block number to write"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + int blockno = arg_get_int_def(ctx, 1, -1); + CLIParserFree(ctx); + + if (blockno < 0) { + PrintAndLogEx(WARNING, "Wrong block number"); + return PM3_EINVARG; + } + + // send read block + uint8_t data[8] = {0}; + int res = topaz_read_block(blockno, data); + if (res == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Block: %0d (0x%02X) [ %s]", blockno, blockno, sprint_hex(data, sizeof(data))); + } + + topaz_switch_off_field(); + return res; +} + +// Write single block +static int CmdHFTopazWrBl(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz wrbl", + "Write a block", + "hf topaz wrbl -b 7 -d 1122334455667788\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "Block number to write"), + arg_str1("d", "data", "", "Block data (8 hex bytes)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int blockno = arg_get_int_def(ctx, 1, -1); + + int dlen = 0; + uint8_t data[8] = {0x00}; + CLIGetHexWithReturn(ctx, 2, data, &dlen); + + CLIParserFree(ctx); + + if (blockno < 0) { + PrintAndLogEx(WARNING, "Wrong block number"); + return PM3_EINVARG; + } + + if (dlen != 8 ) { + PrintAndLogEx(WARNING, "Wrong data length. Expect 8, got %d", dlen); + return PM3_EINVARG; + } + + PrintAndLogEx(INFO, "Block: %0d (0x%02X) [ %s]", blockno, blockno, sprint_hex(data, dlen)); + + // send write Block + int res = topaz_write_erase8_block(blockno, data); + + if (res == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )" ); + } else { + PrintAndLogEx(WARNING, "Write ( " _RED_("fail") " )" ); + } + + topaz_switch_off_field(); + return res; +} + static int CmdHelp(const char *Cmd); static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - {"list", CmdHFTopazList, AlwaysAvailable, "List Topaz history"}, - {"info", CmdHFTopazInfo, IfPm3Iso14443a, "Tag information"}, - {"reader", CmdHFTopazReader, IfPm3Iso14443a, "Act like a Topaz reader"}, - {"sim", CmdHFTopazSim, IfPm3Iso14443a, "Simulate Topaz tag"}, - {"sniff", CmdHFTopazSniff, IfPm3Iso14443a, "Sniff Topaz reader-tag communication"}, - {"raw", CmdHFTopazCmdRaw, IfPm3Iso14443a, "Send raw hex data to tag"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"dump", CmdHFTopazDump, IfPm3Iso14443a, "Dump TOPAZ family tag to file"}, + {"list", CmdHFTopazList, AlwaysAvailable, "List Topaz history"}, + {"info", CmdHFTopazInfo, IfPm3Iso14443a, "Tag information"}, + {"reader", CmdHFTopazReader, IfPm3Iso14443a, "Act like a Topaz reader"}, + {"sim", CmdHFTopazSim, IfPm3Iso14443a, "Simulate Topaz tag"}, + {"sniff", CmdHFTopazSniff, IfPm3Iso14443a, "Sniff Topaz reader-tag communication"}, + {"raw", CmdHFTopazRaw, IfPm3Iso14443a, "Send raw hex data to tag"}, + {"rdbl", CmdHFTopazRdBl, IfPm3Iso14443a, "Read block"}, + {"view", CmdHFTopazView, AlwaysAvailable, "Display content from tag dump file"}, + {"wrbl", CmdHFTopazWrBl, IfPm3Iso14443a, "Write block"}, {NULL, NULL, 0, NULL} }; diff --git a/client/src/cmdhftopaz.h b/client/src/cmdhftopaz.h index 773508798..19dfbb2f9 100644 --- a/client/src/cmdhftopaz.h +++ b/client/src/cmdhftopaz.h @@ -21,8 +21,32 @@ #include "common.h" + +#define TOPAZ_STATIC_MEMORY (0x0F * 8) // 15 blocks with 8 Bytes each +#define TOPAZ_BLOCK_SIZE 8 +#define TOPAZ_MAX_SIZE 512 + +// a struct to describe a memory area which contains lock bits and the corresponding lockable memory area +typedef struct dynamic_lock_area_s { + struct dynamic_lock_area_s *next; + uint16_t byte_offset; // the address of the lock bits + uint16_t size_in_bits; + uint16_t first_locked_byte; // the address of the lockable area + uint16_t bytes_locked_per_bit; +} dynamic_lock_area_t; + +typedef struct topaz_tag_s { + uint8_t HR01[2]; + uint8_t uid[7]; + uint16_t size; + uint8_t data_blocks[TOPAZ_STATIC_MEMORY / 8][8]; // this memory is always there + uint8_t *dynamic_memory; // this memory can be there + dynamic_lock_area_t *dynamic_lock_areas; // lock area descriptors +} topaz_tag_t; + + + int CmdHFTopaz(const char *Cmd); int CmdHFTopazInfo(const char *Cmd); - int readTopazUid(bool loop, bool verbose); #endif diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 99f5395d2..dcfbdfc32 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -28,6 +28,7 @@ #include "util.h" #include "cmdhficlass.h" // pagemap #include "protocols.h" // iclass defines +#include "cmdhftopaz.h" // TOPAZ defines #ifdef _WIN32 #include "scandir.h" @@ -632,6 +633,24 @@ int saveFileJSONex(const char *preferredName, JSONFileType ftype, uint8_t *data, (*callback)(root); break; } + case jsfTopaz: { + topaz_tag_t *tag = (topaz_tag_t *)(void *) data; + JsonSaveStr(root, "FileType", "topaz"); + JsonSaveBufAsHexCompact(root, "$.Card.UID", tag->uid, sizeof(tag->uid)); + JsonSaveBufAsHexCompact(root, "$.Card.H0R1", tag->HR01, sizeof(tag->HR01)); + JsonSaveBufAsHexCompact(root, "$.Card.Size", (uint8_t *)&(tag->size), 2); + for (size_t i = 0; i < TOPAZ_STATIC_MEMORY / 8; i++) { + char path[PATH_MAX_LENGTH] = {0}; + snprintf(path, sizeof(path), "$.blocks.%zu", i); + JsonSaveBufAsHexCompact(root, path, &tag->data_blocks[i][0], TOPAZ_BLOCK_SIZE); + } + + // ICEMAN todo: add dynamic memory. + // uint16_z Size + // uint8_t *dynamic_memory; + + break; + } default: break; } @@ -1277,7 +1296,39 @@ int loadFileJSONex(const char *preferredName, void *data, size_t maxdatalen, siz if (!strcmp(ctype, "legic")) { JsonLoadBufAsHex(root, "$.raw", udata, maxdatalen, datalen); } + + if (!strcmp(ctype, "topaz")) { + topaz_tag_t *mem = (topaz_tag_t *)udata; + JsonLoadBufAsHex(root, "$.Card.UID", mem->uid, sizeof(mem->uid), datalen); + JsonLoadBufAsHex(root, "$.Card.HR01", mem->HR01, sizeof(mem->HR01), datalen); + JsonLoadBufAsHex(root, "$.Card.Size", (uint8_t *)&(mem->size), 2, datalen); + + size_t sptr = 0; + for (int i = 0; i < (TOPAZ_STATIC_MEMORY / 8); i++) { + + if (sptr + TOPAZ_BLOCK_SIZE > maxdatalen) { + retval = PM3_EMALLOC; + goto out; + } + + char blocks[30] = {0}; + snprintf(blocks, sizeof(blocks), "$.blocks.%d", i); + + size_t len = 0; + JsonLoadBufAsHex(root, blocks, &mem->data_blocks[sptr][0], TOPAZ_BLOCK_SIZE, &len); + if (!len) + break; + + sptr += len; + + // ICEMAN todo: add dynamic memory. + // uint16_z Size + // uint8_t *dynamic_memory; + } + + *datalen += sptr; + } out: if (callback != NULL) { diff --git a/client/src/fileutils.h b/client/src/fileutils.h index 758dab84f..d237b0fe7 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -48,6 +48,7 @@ typedef enum { jsfEM4x50, jsfFido, jsfFudan, + jsfTopaz, } JSONFileType; typedef enum { diff --git a/client/src/nfc/ndef.c b/client/src/nfc/ndef.c index f4d07ba7d..bd4e69710 100644 --- a/client/src/nfc/ndef.c +++ b/client/src/nfc/ndef.c @@ -1036,21 +1036,22 @@ int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose) { case 0x01: { indx++; uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "--- " _CYAN_("NDEF Lock Control") " ---"); if (len != 3) { - PrintAndLogEx(WARNING, "NDEF Lock Control block size must be 3 instead of %d.", len); + PrintAndLogEx(WARNING, "NDEF Lock Control block size must be 3 instead of %d", len); } else { uint8_t pages_addr = (ndef[indx] >> 4) & 0x0f; uint8_t byte_offset = ndef[indx] & 0x0f; uint8_t Size = ndef[indx + 1]; uint8_t BytesLockedPerLockBit = (ndef[indx + 2] >> 4) & 0x0f; uint8_t bytes_per_page = ndef[indx + 2] & 0x0f; - PrintAndLogEx(SUCCESS, " Pages addr (number of pages)... %d", pages_addr); - PrintAndLogEx(SUCCESS, "Byte offset (number of bytes)... %d", byte_offset); - PrintAndLogEx(SUCCESS, "Size in bits of the lock area %d. bytes approx %d", Size, Size / 8); - PrintAndLogEx(SUCCESS, " Number of bytes / page... %d", bytes_per_page); - PrintAndLogEx(SUCCESS, "Bytes Locked Per LockBit"); - PrintAndLogEx(SUCCESS, " number of bytes that each dynamic lock bit is able to lock: %d", BytesLockedPerLockBit); + PrintAndLogEx(SUCCESS, " Pages addr (number of pages).... %d", pages_addr); + PrintAndLogEx(SUCCESS, " Byte offset (number of bytes)... %d", byte_offset); + PrintAndLogEx(SUCCESS, " Lock Area size in bits.......... %d ( %d bytes )", Size, Size / 8); + PrintAndLogEx(SUCCESS, " Number of bytes / page... %d", bytes_per_page); + PrintAndLogEx(SUCCESS, " Bytes Locked Per LockBit"); + PrintAndLogEx(SUCCESS, " number of bytes that each dynamic lock bit locks... %d", BytesLockedPerLockBit); } indx += len; break; @@ -1058,18 +1059,19 @@ int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose) { case 0x02: { indx++; uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); - PrintAndLogEx(SUCCESS, "--- " _CYAN_("NDEF Memory Control") " ---"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "--- " _CYAN_("NDEF Memory Control") " ---"); if (len != 3) { - PrintAndLogEx(WARNING, "NDEF Memory Control block size must be 3 instead of %d.", len); + PrintAndLogEx(WARNING, "NDEF Memory Control block size must be 3 instead of %d", len); } else { uint8_t pages_addr = (ndef[indx] >> 4) & 0x0f; uint8_t byte_offset = ndef[indx] & 0x0f; uint8_t Size = ndef[indx + 1]; uint8_t bytes_per_page = ndef[indx + 2] & 0x0f; - PrintAndLogEx(SUCCESS, " Pages addr (number of pages) : %d", pages_addr); - PrintAndLogEx(SUCCESS, "Byte offset (number of bytes) : %d", byte_offset); - PrintAndLogEx(SUCCESS, "Size in bits of the reserved area : %d. bytes approx: %d", Size, Size / 8); - PrintAndLogEx(SUCCESS, " Number of bytes / page : %d", bytes_per_page); + PrintAndLogEx(SUCCESS, "Pages addr (number of pages).... %d", pages_addr); + PrintAndLogEx(SUCCESS, "Byte offset (number of bytes)... %d", byte_offset); + PrintAndLogEx(SUCCESS, "Reserved area size in bits...... %d ( %d bytes )", Size, Size / 8); + PrintAndLogEx(SUCCESS, " Number of bytes / page... %d", bytes_per_page); } indx += len; break; @@ -1077,6 +1079,7 @@ int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose) { case 0x03: { indx++; uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "--- " _CYAN_("NDEF Message") " ---"); if (len == 0) { PrintAndLogEx(SUCCESS, "Found NDEF message w zero length"); @@ -1094,6 +1097,7 @@ int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose) { case 0xfd: { indx++; uint16_t len = ndefTLVGetLength(&ndef[indx], &indx); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "--- " _CYAN_("Proprietary info") " ---"); PrintAndLogEx(SUCCESS, " Can't decode, skipping %d bytes", len); indx += len;