diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d917b82d..70e2bbe24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added support for NTAG424 cards. (@dankar) - Additional fixes to configcard code for keyroll mode based on nfc-iclass output (@Antiklesys) - Added `bind` option for network connections to specify the outbound address and port (@wh201906) - Changed `lf em 4x05 dump` - now supports the `--ns` nosave parameter (@iceman1001) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index b3b6c47c7c..cbf34de6e8 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -20,15 +20,712 @@ #include #include "cmdparser.h" #include "commonutil.h" +#include "comms.h" +#include "iso7816/apduinfo.h" #include "protocols.h" #include "cliparser.h" #include "cmdmain.h" #include "fileutils.h" // saveFile #include "crypto/libpcrypto.h" // aes_decode #include "cmac.h" +#include "cmdhf14a.h" +#include "ui.h" +#include "util.h" +#include "crc32.h" +#define NTAG424_MAX_BYTES 412 -#define NTAG424_MAX_BYTES 412 + +// NTAG424 commands currently implemented +#define NTAG424_CMD_GET_FILE_SETTINGS 0xF5 +#define NTAG424_CMD_CHANGE_FILE_SETTINGS 0x5F +#define NTAG424_CMD_CHANGE_KEY 0xC4 +#define NTAG424_CMD_READ_DATA 0xAD +#define NTAG424_CMD_WRITE_DATA 0x8D +#define NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_1 0x71 +#define NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_2 0xAF + +// +// Original from https://github.com/rfidhacking/node-sdm/ +// +typedef struct sdm_picc_s { + uint8_t tag; + uint8_t uid[7]; + uint8_t cnt[3]; + uint32_t cnt_int; +} sdm_picc_t; + +// -------------- Encryption structs --------------------------- +typedef struct { + uint8_t ti[4]; + uint8_t rnd_a[16]; + uint8_t pd_cap2[6]; + uint8_t pcd_cap2[6]; +} ntag424_ev2_response_t; + +typedef struct { + uint16_t command_counter; + uint8_t ti[4]; + uint8_t encryption[16]; + uint8_t mac[16]; +} ntag424_session_keys_t; + +typedef enum { + COMM_PLAIN, + COMM_MAC, + COMM_FULL +} ntag424_communication_mode_t; + +const CLIParserOption ntag424_communication_mode_options[] = { + {COMM_PLAIN, "plain"}, + {COMM_MAC, "mac"}, + {COMM_FULL, "encrypt"}, + {0, NULL}, +}; + +// -------------- File settings structs ------------------------- +// Enabling this bit in the settings will also reset the read counter to 0 +#define FILE_SETTINGS_OPTIONS_SDM_AND_MIRRORING (1 << 6) + +#define FILE_SETTINGS_SDM_OPTIONS_UID (1 << 7) +#define FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER (1 << 6) +#define FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER_LIMIT (1 << 5) +#define FILE_SETTINGS_SDM_OPTIONS_SDM_ENC_FILE_DATA (1 << 4) +#define FILE_SETTINGS_SDM_OPTIONS_ENCODING_MODE_ASCII (1 << 0) + +typedef struct { + uint8_t sdm_options; + uint8_t sdm_access[2]; + uint8_t sdm_data[8][3]; +} ntag424_file_sdm_settings_t; + +typedef struct { + uint8_t type; + uint8_t options; + uint8_t access[2]; + uint8_t size[3]; + ntag424_file_sdm_settings_t optional_sdm_settings; +} ntag424_file_settings_t; + +#define SETTINGS_WITHOUT_SDM_DATA_SIZE (1+1+2+3+1+2) + +// A different struct is used when actually writing the settings back, +// since we obviously can't change the size or type of a static file. +typedef struct { + uint8_t options; + uint8_t access[2]; + ntag424_file_sdm_settings_t optional_sdm_settings; +} file_settings_write_t; + +// Currently unused functions, commented out due to -Wunused-function +/*static void ntag424_file_settings_set_access_rights(ntag424_file_settings_t *settings, + uint8_t read_write_key, uint8_t change_key, + uint8_t read_key, uint8_t write_key) + +{ + settings->access[0] = read_write_key << 4 | change_key; + settings->access[1] = read_key << 4 | write_key; +}*/ + +// Currently unused functions, commented out due to -Wunused-function +/*static void ntag424_file_settings_set_sdm_access_rights(ntag424_file_settings_t *settings, + uint8_t sdm_meta_read, uint8_t sdm_file_read, uint8_t sdm_ctr_ret) +{ + settings->optional_sdm_settings.sdm_access[1] = sdm_meta_read << 4 | sdm_file_read; + settings->optional_sdm_settings.sdm_access[0] = 0xf << 4 | sdm_ctr_ret; // (0xf is due to reserved for future use) +}*/ + + +static uint8_t ntag424_file_settings_get_sdm_meta_read(const ntag424_file_settings_t *settings) { + return settings->optional_sdm_settings.sdm_access[1] >> 4; +} + +static uint8_t ntag424_file_settings_get_sdm_file_read(const ntag424_file_settings_t *settings) { + return settings->optional_sdm_settings.sdm_access[1] & 0xf; +} + +// Currently unused functions, commented out due to -Wunused-function +/*static uint8_t ntag424_file_settings_get_sdm_ctr_ret(const ntag424_file_settings_t *settings) { + return settings->optional_sdm_settings.sdm_access[0] & 4; +}*/ + +// Calculate the actual size of a file settings struct. A variable number of data is attached +// at the end depending on settings. +static int ntag424_calc_file_settings_size(const ntag424_file_settings_t *settings) { + int size = 7; + + if (settings->options & FILE_SETTINGS_OPTIONS_SDM_AND_MIRRORING) { + size += 3; // sdm_options and sdm_access must be present + + if (settings->optional_sdm_settings.sdm_options & FILE_SETTINGS_SDM_OPTIONS_UID && + ntag424_file_settings_get_sdm_meta_read(settings) == 0xe) { + size += 3; // UIDOffset + } + + if (settings->optional_sdm_settings.sdm_options & FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER && + ntag424_file_settings_get_sdm_meta_read(settings) == 0xe) { + size += 3; // SDMReadCtrOffset + } + + if (ntag424_file_settings_get_sdm_meta_read(settings) <= 0x04) { + size += 3; // PICCDataOffset + } + + if (ntag424_file_settings_get_sdm_file_read(settings) != 0x0f) { + size += 3; // SDMMacInputOffset + } + + if (ntag424_file_settings_get_sdm_file_read(settings) != 0x0f && + settings->optional_sdm_settings.sdm_options & FILE_SETTINGS_SDM_OPTIONS_SDM_ENC_FILE_DATA) { + size += 3; // SDMEncOffset + size += 3; // SDMEncLength + } + + if (ntag424_file_settings_get_sdm_file_read(settings) != 0x0f) { + // Warning, this value has different offsets depending on + // FILE_SETTINGS_SDM_OPTIONS_SDM_ENC_FILE_DATA + size += 3; // SDMMacOffset + } + + if (settings->optional_sdm_settings.sdm_options & FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER_LIMIT) { + size += 3; // SDMReadCtrLimit + } + } + + return size; +} + +static int ntag424_calc_file_write_settings_size(const ntag424_file_settings_t *settings) { + return ntag424_calc_file_settings_size(settings) - 4; +} + +static void ntag424_calc_send_iv(ntag424_session_keys_t *session_keys, uint8_t *out_ivc) { + uint8_t iv_clear[] = { 0xa5, 0x5a, + session_keys->ti[0], session_keys->ti[1], session_keys->ti[2], session_keys->ti[3], + (uint8_t)(session_keys->command_counter), (uint8_t)(session_keys->command_counter >> 8), + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t zero_iv[16] = {0}; + aes_encode(zero_iv, session_keys->encryption, iv_clear, out_ivc, 16); +} + +static void ntag424_calc_recieve_iv(ntag424_session_keys_t *session_keys, uint8_t *out_ivc) { + uint8_t iv_clear[] = { 0x5a, 0xa5, + session_keys->ti[0], session_keys->ti[1], session_keys->ti[2], session_keys->ti[3], + (uint8_t)(session_keys->command_counter), (uint8_t)(session_keys->command_counter >> 8), + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t zero_iv[16] = {0}; + aes_encode(zero_iv, session_keys->encryption, iv_clear, out_ivc, 16); +} + +static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t command, uint8_t *data, uint8_t datalen, uint8_t *out_mac) { + uint8_t mac_input_header[] = { command, + (uint8_t)session_keys->command_counter, (uint8_t)(session_keys->command_counter >> 8), + session_keys->ti[0], session_keys->ti[1], session_keys->ti[2], session_keys->ti[3] + }; + + int mac_input_len = sizeof(mac_input_header) + datalen; + + uint8_t *mac_input = (uint8_t *)malloc(mac_input_len); + memcpy(mac_input, mac_input_header, sizeof(mac_input_header)); + memcpy(&mac_input[sizeof(mac_input_header)], data, datalen); + uint8_t mac[16] = {0}; + mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + datalen, mac); + + for (int i = 0; i < 8; i++) { + out_mac[i] = mac[i * 2 + 1]; + } + + free(mac_input); +} + +static int ntag424_comm_mac_apdu(APDU_t *apdu, int command_header_length, int apdu_max_data_size, ntag424_session_keys_t *session_keys) { + + int size = apdu->lc; + + if (size + 8 > apdu_max_data_size) { + return PM3_EOVFLOW; + } + + ntag424_calc_mac(session_keys, apdu->ins, apdu->data, size, &apdu->data[size]); + session_keys->command_counter++; // CmdCtr should be incremented each time a MAC is calculated + apdu->lc = size + 8; + + return PM3_SUCCESS; +} + +static int ntag424_comm_encrypt_apdu(APDU_t *apdu, int command_header_length, int apdu_max_data_size, ntag424_session_keys_t *session_keys) { + // ------- Calculate IV + uint8_t ivc[16]; + ntag424_calc_send_iv(session_keys, ivc); + + int size = apdu->lc; + + size_t encrypt_data_size = size - command_header_length; + size_t padded_data_size = encrypt_data_size + 16 - (encrypt_data_size % 16); // pad up to 16 byte blocks + uint8_t temp_buffer[256] = {0}; + + if (!encrypt_data_size) { + return PM3_SUCCESS; + } + + if (padded_data_size + command_header_length > apdu_max_data_size) { + return PM3_EOVFLOW; + } + + // ------ Pad data + memcpy(temp_buffer, &apdu->data[command_header_length], encrypt_data_size); // We encrypt everything except the CmdHdr (first byte in data) + temp_buffer[encrypt_data_size] = 0x80; + + // ------ Encrypt it + aes_encode(ivc, session_keys->encryption, temp_buffer, &apdu->data[command_header_length], padded_data_size); + + apdu->lc = (uint8_t)(command_header_length + padded_data_size); // Set size to CmdHdr + padded data + + return PM3_SUCCESS; +} + +static int ntag424_exchange_apdu(APDU_t *apdu, int command_header_length, uint8_t *response, int *response_length, ntag424_communication_mode_t comm_mode, ntag424_session_keys_t *session_keys, uint8_t sw1_expected, uint8_t sw2_expected) { + + int res; + + // New buffer since we might need to expand the data in the apdu + int buffer_length = 256; + uint8_t tmp_apdu_buffer[256] = {0}; + + if (comm_mode != COMM_PLAIN) { + if (session_keys == NULL) { + PrintAndLogEx(ERR, "Non-plain communications mode requested but no session keys supplied"); + return PM3_EINVARG; + } + memcpy(tmp_apdu_buffer, apdu->data, apdu->lc); + apdu->data = tmp_apdu_buffer; + } + + if (comm_mode == COMM_FULL) { + res = ntag424_comm_encrypt_apdu(apdu, command_header_length, buffer_length, session_keys); + if (res != PM3_SUCCESS) { + return res; + } + } + + if (comm_mode == COMM_MAC || comm_mode == COMM_FULL) { + res = ntag424_comm_mac_apdu(apdu, command_header_length, buffer_length, session_keys); + if (res != PM3_SUCCESS) { + return res; + } + } + + uint8_t cmd[256] = {0}; + int apdu_length = 256; + + if (APDUEncode(apdu, cmd, &apdu_length) != 0) { + return PM3_EINVARG; + } + + res = ExchangeAPDU14a(cmd, apdu_length + 1, false, true, response, *response_length, response_length); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to exchange APDU: %d", res); + return res; + } + + if (*response_length < 2) { + PrintAndLogEx(ERR, "No response"); + return PM3_ESOFT; + } + + uint8_t sw1 = response[*response_length - 2]; + uint8_t sw2 = response[*response_length - 1]; + + if (sw1 != sw1_expected || sw2 != sw2_expected) { + PrintAndLogEx(ERR, "Error from card: %02X %02X (%s)", sw1, sw2, GetAPDUCodeDescription(sw1, sw2)); + return PM3_ESOFT; + } + + // Decrypt data if we are in full communications mode. If we want to verify MAC, this + // should also be done here + if (comm_mode == COMM_FULL) { + uint8_t iv[16] = {0}; + ntag424_calc_recieve_iv(session_keys, iv); + + uint8_t tmp[256]; + memcpy(tmp, response, *response_length); + aes_decode(iv, session_keys->encryption, response, tmp, *response_length - 10); + + memcpy(response, tmp, *response_length); + } + + return PM3_SUCCESS; +} + + +static int ntag424_get_file_settings(uint8_t fileno, ntag424_file_settings_t *settings_out) { + int response_length = sizeof(ntag424_file_settings_t) + 2; + uint8_t response[response_length]; + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_GET_FILE_SETTINGS, + .lc = 1, + .data = &fileno, + .extended_apdu = false + }; + + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + if (res != PM3_SUCCESS) { + return res; + } + + if (settings_out) { + memcpy(settings_out, response, response_length); + } + + return PM3_SUCCESS; +} + +static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t *settings, ntag424_session_keys_t *session_keys) { + + // ------- Convert file settings to the format for writing + file_settings_write_t write_settings = { + .options = settings->options, + .access[0] = settings->access[0], + .access[1] = settings->access[1], + .optional_sdm_settings = settings->optional_sdm_settings, + }; + + size_t settings_size = ntag424_calc_file_write_settings_size(settings); + + uint8_t cmd_buffer[256]; + cmd_buffer[0] = fileno; + memcpy(&cmd_buffer[1], &write_settings, settings_size); + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_CHANGE_FILE_SETTINGS, + .lc = 1 + settings_size, + .data = cmd_buffer + }; + + + // ------- Actually send the APDU + int response_length = 8 + 2; + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); + return res; +} + +static void ntag424_print_file_settings(uint8_t fileno, const ntag424_file_settings_t *settings) { + + int num_sdm_data = (ntag424_calc_file_settings_size(settings) - SETTINGS_WITHOUT_SDM_DATA_SIZE) / 3; + + PrintAndLogEx(SUCCESS, "--- " _CYAN_("File %d settings:"), fileno); + + PrintAndLogEx(SUCCESS, " type: " _GREEN_("%02X"), settings->type); + PrintAndLogEx(SUCCESS, " options: " _GREEN_("%02X"), settings->options); + PrintAndLogEx(SUCCESS, " access: " _GREEN_("%02X%02X (RW, C, R, W)"), settings->access[0], settings->access[1]); + PrintAndLogEx(SUCCESS, " size: " _GREEN_("%02X%02X%02X"), settings->size[2], settings->size[1], settings->size[0]); + + if (settings->options & FILE_SETTINGS_OPTIONS_SDM_AND_MIRRORING) { + PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM settings: ")); + PrintAndLogEx(SUCCESS, " options: " _GREEN_("%02X"), settings->optional_sdm_settings.sdm_options); + PrintAndLogEx(SUCCESS, " sdm access: " _GREEN_("%02X%02X"), settings->optional_sdm_settings.sdm_access[0], settings->optional_sdm_settings.sdm_access[1]); + + if (num_sdm_data > 0) { + PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM data: ")); + for (int i = 0; i < num_sdm_data; i++) { + PrintAndLogEx(SUCCESS, " %d: %02X%02X%02X", i, + settings->optional_sdm_settings.sdm_data[i][2], + settings->optional_sdm_settings.sdm_data[i][1], + settings->optional_sdm_settings.sdm_data[i][0]); + } + } + } +} + +// NTAG424 only have one static application, so we select it here +static int ntag424_select_application(void) { + const size_t RESPONSE_LENGTH = 2; + uint8_t cmd[] = {0x00, 0xA4, 0x04, 0x0C, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00 }; + uint8_t resp[RESPONSE_LENGTH]; + int outlen = 0; + int res; + + res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to send apdu"); + return res; + } + + if (outlen != RESPONSE_LENGTH || resp[RESPONSE_LENGTH - 2] != 0x90 || resp[RESPONSE_LENGTH - 1] != 0x00) { + PrintAndLogEx(ERR, "Failed to select application"); + return PM3_ESOFT; + } + + return PM3_SUCCESS; +} + +static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) { + uint8_t key_number[2] = { keyno, 0x00 }; + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_1, + .lc = 0x02, + .data = key_number + }; + + int response_length = 16 + 2; + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, 2, response, &response_length, COMM_PLAIN, NULL, 0x91, 0xAF); + if (res != PM3_SUCCESS) { + return res; + } + + if (response_length != 16 + 2) { + PrintAndLogEx(ERR, "Failed to get RndB (invalid key number?)"); + return PM3_ESOFT; + } + + uint8_t iv[16] = {0}; + aes_decode(iv, key, response, out, 16); + + return PM3_SUCCESS; +} + +static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response_out) { + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_2, + .lc = 0x20, + .data = challenge, + }; + int response_length = 256; + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, 0x20, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + if (res != PM3_SUCCESS) { + return res; + } + + memcpy(response_out, response, response_length - 2); + + return PM3_SUCCESS; +} + +// Authenticate against a key number and optionally get session keys out +static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_session_keys_t *session_keys_out) { + // -------- Get first challenge from card + uint8_t rnd_b_clear[16] = {0}; + + int res = ntag424_auth_first_step(keyno, key, rnd_b_clear); + if (res != PM3_SUCCESS) { + return res; + } + + // -------- Concatenate RndA and RndB and encrypt it with the key + uint8_t concat_clear[32] = {0}; + uint8_t concat_enc[32] = {0}; + // This should of course be completely random, if we cared + // about security + uint8_t rnd_a_clear[16] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf + }; + + uint8_t iv[16] = {0}; + memcpy(&concat_clear[16], &rnd_b_clear[1], 15); + concat_clear[31] = rnd_b_clear[0]; + memcpy(concat_clear, rnd_a_clear, 16); + + aes_encode(iv, key, concat_clear, concat_enc, 32); + + // -------- Do second step with our concatenated encrypted RndA || RndB + uint8_t resp[4 + 16 + 6 + 6]; + res = ntag424_auth_second_step(concat_enc, resp); + if (res != PM3_SUCCESS) { + return res; + } + + ntag424_ev2_response_t response; + aes_decode(iv, key, resp, (uint8_t *)&response, sizeof(ntag424_ev2_response_t)); + + // -------- Verify that the response we got contains the RndA that we supplied (rotated one byte) + if (memcmp(response.rnd_a, &rnd_a_clear[1], 15) != 0 || + response.rnd_a[15] != rnd_a_clear[0]) { + PrintAndLogEx(ERR, "Incorrect response from card\n" + "expected: %s\n" + "got: %s" + , sprint_hex(rnd_a_clear, 16), + sprint_hex(response.rnd_a, 16)); + return PM3_ESOFT; + } + + // -------- Optionally calculate session keys + if (session_keys_out) { + memset(session_keys_out, 0, sizeof(ntag424_session_keys_t)); + memcpy(session_keys_out->ti, response.ti, sizeof(response.ti)); + + // SV 1 = [0xA5][0x5A][0x00][0x01] + // [0x00][0x80][RndA[15:14] || + // [ (RndA[13:8] ⊕ RndB[15:10]) ] || + // [RndB[9:0] || RndA[7:0] + + uint8_t sv1[] = { 0xa5, 0x5a, 0x00, 0x01, 0x00, 0x80, rnd_a_clear[0], rnd_a_clear[1], + rnd_a_clear[2] ^rnd_b_clear[0], + rnd_a_clear[3] ^rnd_b_clear[1], + rnd_a_clear[4] ^rnd_b_clear[2], + rnd_a_clear[5] ^rnd_b_clear[3], + rnd_a_clear[6] ^rnd_b_clear[4], + rnd_a_clear[7] ^rnd_b_clear[5], + rnd_b_clear[6], rnd_b_clear[7], rnd_b_clear[8], rnd_b_clear[9], rnd_b_clear[10], + rnd_b_clear[11], rnd_b_clear[12], rnd_b_clear[13], rnd_b_clear[14], rnd_b_clear[15], + rnd_a_clear[8], rnd_a_clear[9], rnd_a_clear[10], + rnd_a_clear[11], rnd_a_clear[12], rnd_a_clear[13], rnd_a_clear[14], rnd_a_clear[15] + }; + + // SV 2 = [0x5A][0xA5][0x00][0x01] + // [0x00][0x80][RndA[15:14] || + // [ (RndA[13:8] ⊕ RndB[15:10]) ] || + // [RndB[9:0] || RndA[7:0] + + uint8_t sv2[] = { 0x5a, 0xa5, 0x00, 0x01, 0x00, 0x80, rnd_a_clear[0], rnd_a_clear[1], + rnd_a_clear[2] ^rnd_b_clear[0], + rnd_a_clear[3] ^rnd_b_clear[1], + rnd_a_clear[4] ^rnd_b_clear[2], + rnd_a_clear[5] ^rnd_b_clear[3], + rnd_a_clear[6] ^rnd_b_clear[4], + rnd_a_clear[7] ^rnd_b_clear[5], + rnd_b_clear[6], rnd_b_clear[7], rnd_b_clear[8], rnd_b_clear[9], rnd_b_clear[10], + rnd_b_clear[11], rnd_b_clear[12], rnd_b_clear[13], rnd_b_clear[14], rnd_b_clear[15], + rnd_a_clear[8], rnd_a_clear[9], rnd_a_clear[10], + rnd_a_clear[11], rnd_a_clear[12], rnd_a_clear[13], rnd_a_clear[14], rnd_a_clear[15] + }; + + mbedtls_aes_cmac_prf_128(key, 16, sv1, sizeof(sv1), session_keys_out->encryption); + mbedtls_aes_cmac_prf_128(key, 16, sv2, sizeof(sv2), session_keys_out->mac); + } + + return PM3_SUCCESS; +} + +#define MAX_WRITE_APDU (200) + +// Write file to card. Only supports plain communications mode. Authentication must be done +// first unless file has free write access. +static int ntag424_write_data(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *in, ntag424_communication_mode_t comm_mode, ntag424_session_keys_t *session_keys) { + size_t remainder = 0; + + // Split writes that are too large for one APDU + if (num_bytes > MAX_WRITE_APDU) { + remainder = num_bytes - MAX_WRITE_APDU; + num_bytes = MAX_WRITE_APDU; + } + + uint8_t cmd_header[] = { + fileno, + (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset + (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), (uint8_t)(num_bytes >> 16) //size + }; + + uint8_t cmd[256] = {0}; + + memcpy(cmd, cmd_header, sizeof(cmd_header)); + memcpy(&cmd[sizeof(cmd_header)], in, num_bytes); + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_WRITE_DATA, + .lc = sizeof(cmd_header) + num_bytes, + .data = cmd, + }; + + int response_length = 8 + 2; // potential MAC and result + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, sizeof(cmd_header), response, &response_length, comm_mode, session_keys, 0x91, 0x00); + if (res != PM3_SUCCESS) { + return res; + } + + if (remainder > 0) { + return ntag424_write_data(fileno, offset + num_bytes, remainder, &in[num_bytes], comm_mode, session_keys); + } + + return PM3_SUCCESS; +} + +// Read file from card. Only supports plain communications mode. Authentication must be done +// first unless file has free read access. +static int ntag424_read_data(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *out, ntag424_communication_mode_t comm_mode, ntag424_session_keys_t *session_keys) { + uint8_t cmd_header[] = { + fileno, + (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset + (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00 + }; + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_READ_DATA, + .lc = sizeof(cmd_header), + .data = cmd_header, + }; + + int response_length = num_bytes + 4 + 2 + 20; // number of bytes to read + mac + result + potential padding + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, sizeof(cmd_header), response, &response_length, comm_mode, session_keys, 0x91, 0x00); + if (res != PM3_SUCCESS) { + return res; + } + + memcpy(out, response, num_bytes); + return PM3_SUCCESS; +} + +static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, uint8_t version, ntag424_session_keys_t *session_keys) { + // -------- Calculate xor and crc + uint8_t key[16] = {0}; + uint8_t crc[4] = {0}; + if (keyno != 0) { + for (int i = 0; i < 16; i++) { + key[i] = old_key[i] ^ new_key[i]; + } + crc32_ex(new_key, 16, crc); + } else { + memcpy(key, new_key, 16); + } + + // ------- Assemble KeyData command + uint8_t key_cmd_data[32] = {0}; + key_cmd_data[0] = keyno; + memcpy(&key_cmd_data[1], key, 16); + key_cmd_data[17] = version; + int key_data_len; + if (keyno != 0) { + memcpy(&key_cmd_data[18], crc, sizeof(crc)); + key_data_len = sizeof(keyno) + sizeof(key) + sizeof(version) + sizeof(crc); + } else { + key_data_len = sizeof(keyno) + sizeof(key) + sizeof(version); + } + + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_CHANGE_KEY, + .lc = key_data_len, + .data = key_cmd_data + }; + + int response_length = 8 + 2; + uint8_t response[response_length]; + + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); + return res; +} static int CmdHelp(const char *Cmd); @@ -69,127 +766,583 @@ static int CmdHF_ntag424_view(const char *Cmd) { return PM3_SUCCESS; } -// -// Original from https://github.com/rfidhacking/node-sdm/ -// -typedef struct sdm_picc_s { - uint8_t tag; - uint8_t uid[7]; - uint8_t cnt[3]; - uint32_t cnt_int; -} sdm_picc_t; +static int CmdHF_ntag424_info(const char *Cmd) { -static int sdm_generator(void) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 info", + "Get info about NXP NTAG424 DNA Family styled tag.", + "hf ntag424 info" + ); + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + PrintAndLogEx(INFO, "not implemented yet"); + PrintAndLogEx(INFO, "Feel free to contribute!"); + return PM3_SUCCESS; +} - // NXP Secure Dynamic Messaging (SDM) with Secure Unique NFC message (SUN) - // Where do they come up with these names? - // - // ref: - // https://www.nxp.com/docs/en/application-note/AN12196.pdf +static int ntag424_cli_get_auth_information(CLIParserContext *ctx, int key_no_index, int key_index, int *keyno, uint8_t *key_out) { + uint8_t key[16]; + int keylen = 16; + if (keyno) { + *keyno = arg_get_int(ctx, key_no_index); + } + CLIGetHexWithReturn(ctx, key_index, key, &keylen); - // SMD / CMAC - uint8_t iv[16] = {0}; - uint8_t aeskey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - // uint8_t enc_txt[16] = {0xEF, 0x96, 0x3F, 0xF7, 0x82, 0x86, 0x58, 0xA5, 0x99, 0xF3, 0x04, 0x15, 0x10, 0x67, 0x1E, 0x88}; - uint8_t enc_txt[16] = {0xe6, 0x45, 0xb6, 0x15, 0x4e, 0x8f, 0x32, 0x7d, 0xfb, 0xab, 0x93, 0x4d, 0x4c, 0x66, 0x46, 0x14}; - uint8_t dec_txt[16] = {0}; + if (keylen != 16) { + return PM3_ESOFT; + } - aes_decode(iv, aeskey, enc_txt, dec_txt, sizeof(enc_txt)); + memcpy(key_out, key, 16); - PrintAndLogEx(INFO, "Ntag424 SUN message validation and encryption"); - PrintAndLogEx(INFO, "Enc text... %s", sprint_hex(enc_txt, sizeof(enc_txt))); - PrintAndLogEx(INFO, "Dec text... %s", sprint_hex(dec_txt, sizeof(dec_txt))); + return PM3_SUCCESS; +} - sdm_picc_t o = {0}; - o.tag = dec_txt[0]; - memcpy(o.uid, dec_txt + 1, sizeof(o.uid)); - memcpy(o.cnt, dec_txt + 8, sizeof(o.cnt)); - o.cnt_int = MemLeToUint3byte(o.cnt); +static int CmdHF_ntag424_auth(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 auth", + "Authenticate with selected key against NTAG424.", + "hf ntag424 auth -n 0 -k 00000000000000000000000000000000"); - PrintAndLogEx(INFO, "Decypted text"); - PrintAndLogEx(INFO, " Tag........... 0x%02X", o.tag); - PrintAndLogEx(INFO, " UID........... %s", sprint_hex(o.uid, sizeof(o.uid))); - PrintAndLogEx(INFO, " Count bytes... %s", sprint_hex(o.cnt, sizeof(o.cnt))); - PrintAndLogEx(INFO, " Count value... 0x%X ( %u )", o.cnt_int, o.cnt_int); + void *argtable[] = { + arg_param_begin, + arg_int1("n", "keyno", "", "Key number"), + arg_str1("k", "key", "", "Key for authenticate (HEX 16 bytes)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + int keyno; + uint8_t key[16] = {0}; - // SV2 as per NXP DS465430 (NT4H2421Gx Data sheet) - uint8_t sv2data[16] = {0x3C, 0xC3, 0x00, 0x01, 0x00, 0x80}; + if (ntag424_cli_get_auth_information(ctx, 1, 2, &keyno, key) != PM3_SUCCESS) { + CLIParserFree(ctx); + return PM3_ESOFT; + } - memcpy(sv2data + 6, o.uid, sizeof(o.uid)); - memcpy(sv2data + 6 + sizeof(o.uid), o.cnt, sizeof(o.cnt)); + CLIParserFree(ctx); - uint8_t cmackey[16] = {0}; - mbedtls_aes_cmac_prf_128(aeskey, 16, sv2data, sizeof(sv2data), cmackey); + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to select card"); + DropField(); + return res; + } - uint8_t zero[16] = {0}; - uint8_t full_cmac[16] = {0}; - mbedtls_aes_cmac_prf_128(cmackey, 16, zero, 0, full_cmac); + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } - uint8_t cmac[8] = {0}; - for (int i = 0, j = 1; i < 8; ++i, j += 2) { - cmac[i] = full_cmac[j]; + res = ntag424_authenticate_ev2_first(keyno, key, NULL); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); + } else { + PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); } - //uint8_t veri[] = {0x94, 0xee, 0xd9, 0xee, 0x65, 0x33, 0x70, 0x86}; - uint8_t veri[] = {0x8b, 0xa1, 0xfb, 0x47, 0x0d, 0x63, 0x39, 0xe8 }; - uint8_t is_ok = (memcmp(cmac, veri, 8) == 0); + DropField(); - PrintAndLogEx(INFO, "SDM cmac... %s ( %s )", - sprint_hex(cmac, sizeof(cmac)), - (is_ok) ? _GREEN_("ok") : _RED_("fail") - ); + return res; +} - return PM3_SUCCESS; +// Read can only read files with plain communication mode! +static int CmdHF_ntag424_read(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 read", + "Read and print data from file on NTAG424 tag. Will authenticate if key information is provided.", + "hf ntag424 read -f 1 -n 0 -k 00000000000000000000000000000000 -o 0 -l 32\n" + "hf ntag424 read -f 2 -n 0 -k 00000000000000000000000000000000 -o 0 -l 256\n" + "hf ntag424 read -f 3 -n 3 -k 00000000000000000000000000000000 -o 0 -l 128 -m encrypt"); + + void *argtable[] = { + arg_param_begin, + arg_int1("f", "fileno", "", "File number (1-3)"), + arg_int0("n", "keyno", "", "Key number"), + arg_str0("k", "key", "", "Key for authentication (HEX 16 bytes)"), + arg_int0("o", "offset", "", "Offset to read in file (default 0)"), + arg_int1("l", "length", "", "Number of bytes to read"), + arg_str0("m", "cmode", "", "Communicaton mode"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int keyno; + uint8_t key[16] = {0}; + int auth = 1; + + int fileno = arg_get_int(ctx, 1); + + if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { + auth = 0; + } + + ntag424_communication_mode_t comm_mode; + int comm_out = 0; + if (CLIGetOptionList(arg_get_str(ctx, 6), ntag424_communication_mode_options, &comm_out)) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + comm_mode = comm_out; + + if (comm_mode != COMM_PLAIN && auth == 0) { + PrintAndLogEx(ERR, "Only plain communication mode can be used without a key specified"); + return PM3_EINVARG; + } + + int offset = arg_get_int_def(ctx, 4, 0); + int read_length = arg_get_int(ctx, 5); + + CLIParserFree(ctx); + + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + ntag424_session_keys_t session_keys; + if (auth) { + res = ntag424_authenticate_ev2_first(keyno, key, &session_keys); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); + DropField(); + return res; + } else { + PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); + } + } + + uint8_t data[512]; + + res = ntag424_read_data(fileno, offset, read_length, data, comm_mode, &session_keys); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + PrintAndLogEx(SUCCESS, " -------- Read file %d contents ------------ ", fileno); + print_hex_break(data, read_length, 16); + + DropField(); + + return res; } -static int CmdHF_ntag424_sdm(const char *Cmd) { +static int CmdHF_ntag424_write(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "hf ntag424 sdm", - "Validate a SDM message", - "hf ntag424 sdm" - ); + CLIParserInit(&ctx, "hf ntag424 write", + "Write data to file on NTAG424 tag. Will authenticate if key information is provided.", + "hf ntag424 write -f 2 -n 0 -k 00000000000000000000000000000000 -o 0 -d 1122334455667788\n" + "hf ntag424 write -f 3 -n 3 -k 00000000000000000000000000000000 -o 0 -d 1122334455667788 -m encrypt"); + void *argtable[] = { arg_param_begin, + arg_int1("f", "fileno", "", "File number (1-3), (default 2)"), + arg_int0("n", "keyno", "", "Key number"), + arg_str0("k", "key", "", "Key for authentication (HEX 16 bytes)"), + arg_int0("o", "offset", "", "Offset to write in file (default 0)"), + arg_str1("d", "data", "", "Data to write"), + arg_str0("m", "cmode", "", "Communicaton mode"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); + + int keyno; + uint8_t key[16] = {0}; + int auth = 1; + + int fileno = arg_get_int(ctx, 1); + + if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { + auth = 0; + } + + ntag424_communication_mode_t comm_mode; + int comm_out = 0; + if (CLIGetOptionList(arg_get_str(ctx, 6), ntag424_communication_mode_options, &comm_out)) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + comm_mode = comm_out; + + if (comm_mode != COMM_PLAIN && auth == 0) { + PrintAndLogEx(ERR, "Only plain communication mode can be used without a key specified"); + return PM3_EINVARG; + } + + int offset = arg_get_int_def(ctx, 4, 0); + + uint8_t data[512] = {0}; + int datalen = 512; + CLIGetHexWithReturn(ctx, 5, data, &datalen); + CLIParserFree(ctx); - return sdm_generator(); + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + ntag424_session_keys_t session_keys; + if (auth) { + res = ntag424_authenticate_ev2_first(keyno, key, &session_keys); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); + DropField(); + return res; + } else { + PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); + } + } + + res = ntag424_write_data(fileno, offset, datalen, data, comm_mode, &session_keys); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + PrintAndLogEx(SUCCESS, "Wrote %d bytes", datalen); + + DropField(); + + return res; } -static int CmdHF_ntag424_info(const char *Cmd) { +static int CmdHF_ntag424_getfilesettings(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 getfilesettings", + "Read and print file settings for file", + "hf ntag424 getfilesettings -f 2"); + void *argtable[] = { + arg_param_begin, + arg_int1("f", "file", "", "File number"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + int fileno = arg_get_int(ctx, 1); + + CLIParserFree(ctx); + + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + ntag424_file_settings_t settings; + res = ntag424_get_file_settings(fileno, &settings); + DropField(); + if (res != PM3_SUCCESS) { + return res; + } + + ntag424_print_file_settings(fileno, &settings); + + + return res; +} + +static int CmdHF_ntag424_changefilesettings(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "hf ntag424 info", - "Get info about NXP NTAG424 DNA Family styled tag.", - "hf ntag424 info" - ); + CLIParserInit(&ctx, "hf ntag424 changefilesettings", + "Updates file settings for file, must be authenticated.\n" + "This is a short explanation of the settings. See AN12196 for more information:\n" + "options: byte with bit flags\n" + " Bit: Setting:\n" + " 6 Enable SDM and mirroring\n\n" + + "access: two byte access rights.\n" + "Each nibble is a key number, or E for free access.\n" + "Order is key for readwrite, change, read and write\n\n" + + "sdmoptions: byte with bit flags\n" + " Bit: Setting:\n" + " 0 ASCII encoding\n" + " 4 SDMEncFileData\n" + " 5 SDMReadCtrLimit\n" + " 6 SDMReadCtr\n" + " 7 SDMOptionsUID\n\n" + + "sdmaccess: two byte access rights.\n" + "Each nibble is a key, or E for plain mirror and F for no mirroring\n" + "Order is Reserved, SDMCtrRet, SDMMetaRead and SDMFileRead\n\n" + + "sdm_data: Three bytes of data used to control SDM settings. Can be specified multiple times.\n" + "Data means different things depending on settings.\n\n" + + "Note: Not all of these settings will be written. It depends on the option byte, and the keys set. See AN12196 for more information.\n" + "You must also start with sdmdata1, then sdmdata2, up to the number of sdm_data you want to write", + + + "hf ntag424 changefilesettings -f 2 -n 0 -k 00000000000000000000000000000000 -o 40 -a 00E0 -s C1 -c F000 --sdmdata1 000020 --sdmdata2 000043 --sdmdata3 000043"); + void *argtable[] = { arg_param_begin, + arg_int1("f", "file", "", "File number"), + arg_int1("n", "keyno", "", "Key number"), + arg_str1("k", "key", "", "Key for authentication (HEX 16 bytes)"), + arg_str0("o", "options", "", "File options byte (HEX 1 byte)"), + arg_str0("a", "access", "", "File access settings (HEX 2 bytes)"), + arg_str0("s", "sdmoptions", "", "SDM options (HEX 1 byte)"), + arg_str0("c", "sdmaccess", "", "SDM access settings (HEX 2 bytes)"), + arg_str0(NULL, "sdmdata1", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata2", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata3", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata4", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata5", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata6", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata7", "", "SDM data (HEX 3 bytes)"), + arg_str0(NULL, "sdmdata8", "", "SDM data (HEX 3 bytes)"), + // Sorry, couldn't figure out how to work with arg_strn... arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); + int fileno = arg_get_int(ctx, 1); + + int keyno; + uint8_t key[16] = {0}; + + uint8_t has_options = 0; + uint8_t options[1]; + uint8_t has_access = 0; + uint8_t access[2]; + uint8_t has_sdmoptions = 0; + uint8_t sdmoptions[1]; + uint8_t has_sdmaccess = 0; + uint8_t sdmaccess[2]; + uint8_t num_sdm_data = 0; + uint8_t sdm_data[8][3]; + + if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Could not get key settings"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + int len = 1; + if (arg_get_str(ctx, 4)->count == 1) { + has_options = 1; + CLIGetHexWithReturn(ctx, 4, options, &len); + if (len != 1) { + PrintAndLogEx(ERR, "Options must be 1 byte"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + len = 2; + if (arg_get_str(ctx, 5)->count == 1) { + has_access = 1; + CLIGetHexWithReturn(ctx, 5, access, &len); + if (len != 2) { + PrintAndLogEx(ERR, "Access must be 2 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + len = 1; + if (arg_get_str(ctx, 6)->count == 1) { + has_sdmoptions = 1; + CLIGetHexWithReturn(ctx, 6, sdmoptions, &len); + if (len != 1) { + PrintAndLogEx(ERR, "SDM Options must be 1 byte"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + len = 2; + if (arg_get_str(ctx, 7)->count == 1) { + has_sdmaccess = 1; + CLIGetHexWithReturn(ctx, 7, sdmaccess, &len); + if (len != 2) { + PrintAndLogEx(ERR, "SDM Access must be 2 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + + for (int i = 0; i < 8; i++) { + if (arg_get_str(ctx, 8 + i)->count == 1) { + len = 3; + num_sdm_data++; + CLIGetHexWithReturn(ctx, 8 + i, sdm_data[i], &len); + if (len != 3) { + PrintAndLogEx(ERR, "sdmdata must be 3 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } else { + break; + } + } + + CLIParserFree(ctx); - PrintAndLogEx(INFO, "not implemented yet"); - PrintAndLogEx(INFO, "Feel free to contribute!"); + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + ntag424_file_settings_t settings; + res = ntag424_get_file_settings(fileno, &settings); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + ntag424_session_keys_t session = {0}; + res = ntag424_authenticate_ev2_first(keyno, key, &session); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); + DropField(); + return res; + } - // has hardcoded application and three files. + if (has_options) { + settings.options = options[0]; + } + if (has_access) { + memcpy(settings.access, access, 2); + } + if (has_sdmoptions) { + settings.optional_sdm_settings.sdm_options = sdmoptions[0]; + } + if (has_sdmaccess) { + memcpy(settings.optional_sdm_settings.sdm_access, sdmaccess, 2); + } + for (int i = 0; i < num_sdm_data; i++) { + settings.optional_sdm_settings.sdm_data[i][2] = sdm_data[i][0]; + settings.optional_sdm_settings.sdm_data[i][1] = sdm_data[i][1]; + settings.optional_sdm_settings.sdm_data[i][0] = sdm_data[i][2]; + } - /* - // Check if the tag reponds to APDUs. - PrintAndLogEx(INFO, "Sending a test APDU (select file command) to check if the tag is responding to APDU"); - param_gethex_to_eol("00a404000aa000000440000101000100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); - int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, true, false, response, sizeof(response), &response_n); - if (res != PM3_SUCCESS) { - PrintAndLogEx(FAILED, "Tag did not respond to a test APDU (select file command). Aborting..."); - return res; + if (ntag424_write_file_settings(fileno, &settings, &session) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to write settings"); + DropField(); + return PM3_ESOFT; + } + PrintAndLogEx(SUCCESS, "Wrote settings successfully"); + ntag424_print_file_settings(fileno, &settings); + + DropField(); + return res; +} + +static int CmdHF_ntag424_changekey(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 changekey", + "Change a key.\n" + "Authentication key must currently be different to the one we want to change.\n", + "hf ntag424 changekey -n 1 --oldkey 00000000000000000000000000000000 --newkey 11111111111111111111111111111111 --key0 00000000000000000000000000000000 -v 1\n" + "hf ntag424 changekey -n 0 --newkey 11111111111111111111111111111111 --key0 00000000000000000000000000000000 -v 1\n" + ); + void *argtable[] = { + arg_param_begin, + arg_int1("n", "keyno", "", "Key number to change"), + arg_str0(NULL, "oldkey", "", "Old key (only needed when changing key 1-4, HEX 16 bytes)"), + arg_str1(NULL, "newkey", "", "New key (HEX 16 bytes)"), + arg_str1(NULL, "key0", "", "Authentication key (must be key 0, HEX 16 bytes)"), + arg_int1("v", "version", "", "Version of the new key"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + uint8_t version = arg_get_int(ctx, 6); + int keyno = arg_get_int(ctx, 1); + uint8_t oldkey[16]; + uint8_t newkey[16]; + uint8_t authkey[16]; + + if (keyno != 0) { + if (ntag424_cli_get_auth_information(ctx, 0, 2, NULL, oldkey) != PM3_SUCCESS) { + + PrintAndLogEx(ERR, "Could not get keyno or old key"); + CLIParserFree(ctx); + return PM3_EINVARG; } - */ + } + if (ntag424_cli_get_auth_information(ctx, 0, 3, NULL, newkey) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Could not get new key"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (ntag424_cli_get_auth_information(ctx, 0, 4, NULL, authkey) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Could not get authentication key"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + CLIParserFree(ctx); + + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + res = ntag424_select_application(); + if (res != PM3_SUCCESS) { + DropField(); + return res; + } + + ntag424_session_keys_t session = {0}; + res = ntag424_authenticate_ev2_first(0, authkey, &session); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to authenticate"); + return PM3_ESOFT; + } else { + PrintAndLogEx(SUCCESS, "Successfully authenticated"); + } + + res = ntag424_change_key(keyno, newkey, oldkey, version, &session); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(ERR, "Failed to change key"); + DropField(); + return PM3_ESOFT; + } else { + PrintAndLogEx(SUCCESS, "Successfully changed key %d", keyno); + } + DropField(); return PM3_SUCCESS; } @@ -197,12 +1350,16 @@ static int CmdHF_ntag424_info(const char *Cmd) { // Menu Stuff //------------------------------------ static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"}, - {"info", CmdHF_ntag424_info, IfPm3Iso14443a, "Tag information"}, -// {"ndefread", CmdHF_ntag424_sdm, IfPm3Iso14443a, "Prints NDEF records from card"}, - {"sdm", CmdHF_ntag424_sdm, IfPm3Iso14443a, "Prints NDEF records from card"}, - {"view", CmdHF_ntag424_view, AlwaysAvailable, "Display content from tag dump file"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"}, + {"info", CmdHF_ntag424_info, IfPm3Iso14443a, "Tag information (not implemented yet)"}, + {"view", CmdHF_ntag424_view, AlwaysAvailable, "Display content from tag dump file"}, + {"auth", CmdHF_ntag424_auth, IfPm3Iso14443a, "Test authentication with key"}, + {"read", CmdHF_ntag424_read, IfPm3Iso14443a, "Read file"}, + {"write", CmdHF_ntag424_write, IfPm3Iso14443a, "Write file"}, + {"getfilesettings", CmdHF_ntag424_getfilesettings, IfPm3Iso14443a, "Get file settings"}, + {"changefilesettings", CmdHF_ntag424_changefilesettings, IfPm3Iso14443a, "Change file settings"}, + {"changekey", CmdHF_ntag424_changekey, IfPm3Iso14443a, "Change key"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index cbc0632d79..5b3720d85e 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -455,8 +455,13 @@ const static vocabulary_t vocabulary[] = { { 1, "hf mfdes test" }, { 1, "hf ntag424 help" }, { 0, "hf ntag424 info" }, - { 0, "hf ntag424 sdm" }, { 1, "hf ntag424 view" }, + { 0, "hf ntag424 auth" }, + { 0, "hf ntag424 read" }, + { 0, "hf ntag424 write" }, + { 0, "hf ntag424 getfilesettings" }, + { 0, "hf ntag424 changefilesettings" }, + { 0, "hf ntag424 changekey" }, { 1, "hf seos help" }, { 0, "hf seos info" }, { 1, "hf seos list" }, diff --git a/doc/commands.json b/doc/commands.json index 6355595266..12bc7a5e14 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6790,6 +6790,78 @@ ], "usage": "hf mfu wrbl [-hl] [-k ] -b -d [--force]" }, + "hf ntag424 auth": { + "command": "hf ntag424 auth", + "description": "Authenticate with selected key against NTAG424.", + "notes": [ + "hf ntag424 auth -n 0 -k 00000000000000000000000000000000" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-n, --keyno Key number", + "-k, --key Key for authenticate (HEX 16 bytes)" + ], + "usage": "hf ntag424 auth [-h] -n -k " + }, + "hf ntag424 changefilesettings": { + "command": "hf ntag424 changefilesettings", + "description": "Updates file settings for file, must be authenticated. This is a short explanation of the settings. See AN12196 for more information: options: byte with bit flags Bit: Setting: 6 Enable SDM and mirroring access: two byte access rights. Each nibble is a key number, or E for free access. Order is key for readwrite, change, read and write sdmoptions: byte with bit flags Bit: Setting: 0 ASCII encoding 4 SDMEncFileData 5 SDMReadCtrLimit 6 SDMReadCtr 7 SDMOptionsUID sdmaccess: two byte access rights. Each nibble is a key, or E for plain mirror and F for no mirroring Order is Reserved, SDMCtrRet, SDMMetaRead and SDMFileRead sdm_data: Three bytes of data used to control SDM settings. Can be specified multiple times. Data means different things depending on settings. Note: Not all of these settings will be written. It depends on the option byte, and the keys set. See AN12196 for more information. You must also start with sdmdata1, then sdmdata2, up to the number of sdm_data you want to write", + "notes": [ + "hf ntag424 changefilesettings -f 2 -n 0 -k 00000000000000000000000000000000 -o 40 -a 00E0 -s C1 -c F000 --sdmdata1 000020 --sdmdata2 000043 --sdmdata3 000043" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file File number", + "-n, --keyno Key number", + "-k, --key Key for authentication (HEX 16 bytes)", + "-o, --options File options byte (HEX 1 byte)", + "-a, --access File access settings (HEX 2 bytes)", + "-s, --sdmoptions SDM options (HEX 1 byte)", + "-c, --sdmaccess SDM access settings (HEX 2 bytes)", + "--sdmdata1 SDM data (HEX 3 bytes)", + "--sdmdata2 SDM data (HEX 3 bytes)", + "--sdmdata3 SDM data (HEX 3 bytes)", + "--sdmdata4 SDM data (HEX 3 bytes)", + "--sdmdata5 SDM data (HEX 3 bytes)", + "--sdmdata6 SDM data (HEX 3 bytes)", + "--sdmdata7 SDM data (HEX 3 bytes)", + "--sdmdata8 SDM data (HEX 3 bytes)" + ], + "usage": "hf ntag424 changefilesettings [-h] -f -n -k [-o ] [-a ] [-s ] [-c ] [--sdmdata1 ] [--sdmdata2 ] [--sdmdata3 ] [--sdmdata4 ] [--sdmdata5 ] [--sdmdata6 ] [--sdmdata7 ] [--sdmdata8 ]" + }, + "hf ntag424 changekey": { + "command": "hf ntag424 changekey", + "description": "Change a key. Authentication key must currently be different to the one we want to change.", + "notes": [ + "hf ntag424 changekey -n 1 --oldkey 00000000000000000000000000000000 --newkey 11111111111111111111111111111111 --key0 00000000000000000000000000000000 -v 1", + "hf ntag424 changekey -n 0 --newkey 11111111111111111111111111111111 --key0 00000000000000000000000000000000 -v 1" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-n, --keyno Key number to change", + "--oldkey Old key (only needed when changing key 1-4, HEX 16 bytes)", + "--newkey New key (HEX 16 bytes)", + "--key0 Authentication key (must be key 0, HEX 16 bytes)", + "-v, --version Version of the new key" + ], + "usage": "hf ntag424 changekey [-h] -n [--oldkey ] --newkey --key0 -v " + }, + "hf ntag424 getfilesettings": { + "command": "hf ntag424 getfilesettings", + "description": "Read and print file settings for file", + "notes": [ + "hf ntag424 getfilesettings -f 2" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --file File number" + ], + "usage": "hf ntag424 getfilesettings [-h] -f " + }, "hf ntag424 info": { "command": "hf ntag424 info", "description": "Get info about NXP NTAG424 DNA Family styled tag.", @@ -6802,17 +6874,23 @@ ], "usage": "hf ntag424 info [-h]" }, - "hf ntag424 sdm": { - "command": "hf ntag424 sdm", - "description": "Validate a SDM message", + "hf ntag424 read": { + "command": "hf ntag424 read", + "description": "Read and print data from file on NTAG424 tag. Will authenticate if key information is provided.", "notes": [ - "hf ntag424 sdm" + "hf ntag424 read -f 2 -n 0 -k 00000000000000000000000000000000 -o 0 -l 256" ], "offline": false, "options": [ - "-h, --help This help" + "-h, --help This help", + "-f, --fileno File number (1-3), (default 2)", + "-n, --keyno Key number", + "-k, --key Key for authentication (HEX 16 bytes)", + "-o, --offset Offset to read in file (default 0)", + "-l, --length Number of bytes to read", + "-m, --cmode Communicaton mode" ], - "usage": "hf ntag424 sdm [-h]" + "usage": "hf ntag424 read [-h] -f [-n ] [-k ] [-o ] -l [-m ]" }, "hf ntag424 view": { "command": "hf ntag424 view", @@ -6828,6 +6906,24 @@ ], "usage": "hf ntag424 view [-hv] -f " }, + "hf ntag424 write": { + "command": "hf ntag424 write", + "description": "Write data to file on NTAG424 tag. Will authenticate if key information is provided.", + "notes": [ + "hf ntag424 write -f 2 -n 0 -k 00000000000000000000000000000000 -o 0 -d 1122334455667788" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-f, --fileno File number (1-3), (default 2)", + "-n, --keyno Key number", + "-k, --key Key for authentication (HEX 16 bytes)", + "-o, --offset Offset to write in file (default 0)", + "-d, --data Data to write", + "-m, --cmode Communicaton mode" + ], + "usage": "hf ntag424 write [-h] -f [-n ] [-k ] [-o ] -d [-m ]" + }, "hf plot": { "command": "hf plot", "description": "Plots HF signal after RF signal path and A/D conversion.", @@ -8053,9 +8149,10 @@ "options": [ "-h, --help This help", "-p, --pwd password (00000000)", - "-f, --file override filename prefix (optional). Default is based on UID" + "-f, --file override filename prefix (optional). Default is based on UID", + "--ns no save to file" ], - "usage": "lf em 4x05 dump [-h] [-p ] [-f ]" + "usage": "lf em 4x05 dump [-h] [-p ] [-f ] [--ns]" }, "lf em 4x05 info": { "command": "lf em 4x05 info", @@ -11915,8 +12012,8 @@ } }, "metadata": { - "commands_extracted": 691, + "commands_extracted": 696, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2023-10-22T12:20:10" + "extracted_on": "2023-10-29T15:43:05" } } \ No newline at end of file diff --git a/doc/commands.md b/doc/commands.md index a2059a6661..43eeb35350 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -649,9 +649,14 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- |`hf ntag424 help `|Y |`This help` -|`hf ntag424 info `|N |`Tag information` -|`hf ntag424 sdm `|N |`Prints NDEF records from card` +|`hf ntag424 info `|N |`Tag information (not implemented yet)` |`hf ntag424 view `|Y |`Display content from tag dump file` +|`hf ntag424 auth `|N |`Test authentication with key` +|`hf ntag424 read `|N |`Read file` +|`hf ntag424 write `|N |`Write file` +|`hf ntag424 getfilesettings`|N |`Get file settings` +|`hf ntag424 changefilesettings`|N |`Change file settings` +|`hf ntag424 changekey `|N |`Change key` ### hf seos