From 70422937f228c7536b6139a498962a5fb2bf8a29 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Wed, 25 Oct 2023 17:59:07 +0200 Subject: [PATCH 01/10] Partial implementation of NTAG424 commands Noteworthy stuff that's missing: * Encrypted read/write to file. * Handling of CC and Proprietary file Stuff that works: * Read/write file * Get/change file settings * Change auth keys * Configuring SDM/SUN feature --- client/src/cmdhfntag424.c | 1335 +++++++++++++++++++++++++++++-- client/src/pm3line_vocabulary.h | 9 +- 2 files changed, 1269 insertions(+), 75 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index b3b6c47c7c..725d2cdc35 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -20,13 +20,16 @@ #include #include "cmdparser.h" #include "commonutil.h" +#include "comms.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 "util.h" +#include "crc32.h" #define NTAG424_MAX_BYTES 412 @@ -79,83 +82,679 @@ typedef struct sdm_picc_s { uint32_t cnt_int; } sdm_picc_t; -static int sdm_generator(void) { +// -------------- 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; + +// -------------- File settings structs ------------------------- +#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) +// RFU stuff... +#define FILE_SETTINGS_SDM_OPTIONS_ENCODING_MODE_ASCII (1 << 0) + +typedef struct { + uint8_t sdm_options; + uint8_t sdm_access[2]; + uint8_t offset[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_OFFSETS_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 rfu, 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] = rfu << 4 | sdm_ctr_ret; +}*/ + + +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_rfu(const ntag424_file_settings_t *settings) { + return settings->optional_sdm_settings.sdm_access[0] >> 4; +} + +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_read_file_settings(uint8_t fileno, ntag424_file_settings_t *settings_out) { + const size_t RESPONSE_LENGTH = sizeof(ntag424_file_settings_t) + 2; + uint8_t cmd[] = { 0x90, 0xF5, 0x00, 0x00, 0x01, fileno, 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 < 9) { + PrintAndLogEx(ERR, "Incorrect response asdaslength: %d", outlen); + return PM3_ESOFT; + } + + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Failed to get file settings"); + return PM3_ESOFT; + } + + if(settings_out) + { + memcpy(settings_out, resp, outlen-2); + } + + 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 +// Write file settings is done with full communication mode. This can probably be broken out +// and used for read/write of file when full communication mode is needed. +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, + }; + + + // ------- Calculate IV + 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}; + uint8_t ivc[16] = {0}; + aes_encode(zero_iv, session_keys->encryption, iv_clear, ivc, 16); + + // ------- Encrypt file settings + uint8_t padded_cmddata_buffer[256] = {0}; + uint8_t encrypted_cmddata[256] = {0}; + size_t settings_size = ntag424_calc_file_settings_size(settings) - 4; // This is weird, but since the write settings are the same as + // the settings read out, but minus file type and file size, we subtract 4 here. + + size_t total_size = settings_size + 16 - (settings_size % 16); // pad up to 16 byte blocks + memcpy(padded_cmddata_buffer, (void*)&write_settings, settings_size); + if(total_size > settings_size) { + padded_cmddata_buffer[settings_size] = 0x80; + } + aes_encode(ivc, session_keys->encryption, padded_cmddata_buffer, encrypted_cmddata, total_size); + + // ------- Calculate MAC + uint8_t mac_input_header[] = { 0x5f, + (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], + fileno // CmdHeader + }; + + uint8_t mac_input[256] = {0}; + memcpy(mac_input, mac_input_header, sizeof(mac_input_header)); + memcpy(&mac_input[sizeof(mac_input_header)], encrypted_cmddata, total_size); + uint8_t mac[16] = {0}; + mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + total_size, mac); + uint8_t mact[8] = { mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15] }; + + // ------- Assemble the actual command + uint8_t lc = 1 + total_size + 8; // CmdHeader + size + mac*/ + + uint8_t cmd_header[] = { + 0x90, 0x5f, 0x00, 0x00, + lc, + fileno + }; + + uint8_t cmd[256] = {0}; + + memcpy(cmd, cmd_header, sizeof(cmd_header)); + memcpy(&cmd[sizeof(cmd_header)], encrypted_cmddata, total_size); + memcpy(&cmd[sizeof(cmd_header) + total_size], mact, sizeof(mact)); + cmd[sizeof(cmd_header) + total_size + sizeof(mact)] = 0x00; + + size_t apdu_size = sizeof(cmd_header) + total_size + sizeof(mact) + 1; + + // ------- Actually send the APDU + const size_t RESPONSE_LENGTH = 8 + 2; + int outlen; + uint8_t resp[RESPONSE_LENGTH]; + int res = ExchangeAPDU14a(cmd, apdu_size, false, true, resp, RESPONSE_LENGTH, &outlen); + if(res != PM3_SUCCESS) + { + PrintAndLogEx(ERR, "Failed to send apdu"); + return res; + } + + if(outlen != RESPONSE_LENGTH) { + PrintAndLogEx(ERR, "Incorrect response asdaslength: %d, %02X%02X", outlen, resp[outlen-2], resp[outlen-1]); + return PM3_ESOFT; + } + + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Failed to get file settings"); + return PM3_ESOFT; + } + + session_keys->command_counter++; // Should this be incremented only on success? + + return PM3_SUCCESS; +} + +static void ntag424_print_file_settings(uint8_t fileno, const ntag424_file_settings_t *settings) { + + int num_offsets = (ntag424_calc_file_settings_size(settings) - SETTINGS_WITHOUT_OFFSETS_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_offsets > 0) + { + PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM offsets: ")); + for(int i = 0; i < num_offsets; i++) + { + PrintAndLogEx(SUCCESS, " offset %d: %02X%02X%02X", i, + settings->optional_sdm_settings.offset[i][2], + settings->optional_sdm_settings.offset[i][1], + settings->optional_sdm_settings.offset[i][0]); + } + } + } + + + // Add printing of variable number of offsets here... +} + +// 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) +{ + const size_t RESPONSE_LENGTH = 16 + 2; + uint8_t cmd[] = {0x90, 0x71, 0x00, 0x00, 0x02, keyno, 0x00, 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] != 0x91 || resp[RESPONSE_LENGTH-1] != 0xAF) + { + PrintAndLogEx(ERR, "Failed to get RndB (invalid key number?)"); + return PM3_ESOFT; + } - // 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}; + aes_decode(iv, key, resp, out, 16); + + return PM3_SUCCESS; +} + +static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response) +{ + uint8_t cmd_header[] = { 0x90, 0xAF, 0x00, 0x00, 0x20 }; + + uint8_t cmd[sizeof(cmd_header) + 32 + 1] = {0}; + + memcpy(cmd, cmd_header, sizeof(cmd_header)); + memcpy(&cmd[sizeof(cmd_header)], challenge, 32); + + const size_t RESPONSE_LENGTH = 256; + 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) + { + return res; + } - aes_decode(iv, aeskey, enc_txt, dec_txt, sizeof(enc_txt)); + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Challenge failed: wrong key?"); + return PM3_ESOFT; + } + + memcpy(response, resp, outlen-2); + + return PM3_SUCCESS; +} - 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))); +// 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}; - 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); + int res = ntag424_auth_first_step(keyno, key, rnd_b_clear); + if(res != PM3_SUCCESS) + { + return res; + } - 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); + // -------- 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 + }; - // SV2 as per NXP DS465430 (NT4H2421Gx Data sheet) - uint8_t sv2data[16] = {0x3C, 0xC3, 0x00, 0x01, 0x00, 0x80}; + 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); - memcpy(sv2data + 6, o.uid, sizeof(o.uid)); - memcpy(sv2data + 6 + sizeof(o.uid), o.cnt, sizeof(o.cnt)); + aes_encode(iv, key, concat_clear, concat_enc, 32); - uint8_t cmackey[16] = {0}; - mbedtls_aes_cmac_prf_128(aeskey, 16, sv2data, sizeof(sv2data), cmackey); + // -------- 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; + } - uint8_t zero[16] = {0}; - uint8_t full_cmac[16] = {0}; - mbedtls_aes_cmac_prf_128(cmackey, 16, zero, 0, full_cmac); + ntag424_ev2_response_t response; + aes_decode(iv, key, resp, (uint8_t*)&response, sizeof(ntag424_ev2_response_t)); - uint8_t cmac[8] = {0}; - for (int i = 0, j = 1; i < 8; ++i, j += 2) { - cmac[i] = full_cmac[j]; + // -------- 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; } - //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); + // -------- 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)); - PrintAndLogEx(INFO, "SDM cmac... %s ( %s )", - sprint_hex(cmac, sizeof(cmac)), - (is_ok) ? _GREEN_("ok") : _RED_("fail") - ); + // 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; } -static int CmdHF_ntag424_sdm(const char *Cmd) { - CLIParserContext *ctx; - CLIParserInit(&ctx, "hf ntag424 sdm", - "Validate a SDM message", - "hf ntag424 sdm" - ); - void *argtable[] = { - arg_param_begin, - arg_param_end +#define MAX_WRITE_APDU 248 + +// Write file to card. Only supports plain communications mode. Authentication must be done +// first unless file has free write access. +static int ntag424_write_file(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *in) { + const size_t RESPONSE_LENGTH = 2; + size_t remainder = 0; + + if(num_bytes > MAX_WRITE_APDU) + { + remainder = num_bytes - MAX_WRITE_APDU; + num_bytes = MAX_WRITE_APDU; + } + + // 248 + + uint8_t cmd_header[] = { 0x90, 0x8d, 0x00, 0x00, 0x07 + num_bytes, 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 }; - CLIExecWithReturn(ctx, Cmd, argtable, false); - CLIParserFree(ctx); - return sdm_generator(); + uint8_t cmd[512] = {0}; + + memcpy(cmd, cmd_header, sizeof(cmd_header)); + memcpy(&cmd[sizeof(cmd_header)], in, num_bytes); + + size_t total_size = sizeof(cmd_header) + num_bytes + 1; //(Le) + + uint8_t resp[RESPONSE_LENGTH]; + int outlen = 0; + int res; + + res = ExchangeAPDU14a(cmd, total_size, false, true, resp, RESPONSE_LENGTH, &outlen); + if(res != PM3_SUCCESS) + { + PrintAndLogEx(ERR, "Failed to send apdu"); + return res; + } + + if(outlen != RESPONSE_LENGTH) { + PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); + return PM3_ESOFT; + } + + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Failed to write file"); + return PM3_ESOFT; + } + + if(remainder > 0) + { + return ntag424_write_file(fileno, offset + num_bytes, remainder, &in[num_bytes]); + } + + 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_file(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *out) { + const size_t RESPONSE_LENGTH = num_bytes + 2; + + uint8_t cmd[] = { 0x90, 0xad, 0x00, 0x00, 0x07, fileno, + (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset + (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00, //size + 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) { + PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); + return PM3_ESOFT; + } + + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Failed to read file"); + return PM3_ESOFT; + } + + memcpy(out, resp, 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); + } + + + // ------- Calculate IV + 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}; + uint8_t ive[16] = {0}; + aes_encode(zero_iv, session_keys->encryption, iv_clear, ive, 16); + + // ------- Calculate KeyData + uint8_t keydata[32] = {0}; + memcpy(keydata, key, 16); + keydata[16] = version; + if(keyno != 0) + { + memcpy(&keydata[17], crc, 4); + keydata[21] = 0x80; + } + else + { + keydata[17] = 0x80; + } + + uint8_t enc_keydata[32] = {0}; + aes_encode(ive, session_keys->encryption, keydata, enc_keydata, 32); + + // -------- Calculate MAC + uint8_t mac_input_header[] = { 0xC4, + (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], + keyno, + }; + + uint8_t mac_input[256] = {0}; + memcpy(mac_input, mac_input_header, sizeof(mac_input_header)); + memcpy(&mac_input[sizeof(mac_input_header)], enc_keydata, 32); + uint8_t mac[16] = {0}; + mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + 32, mac); + uint8_t mact[8] = { mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15] }; + + + // ------- Assemble APDU + uint8_t cmd_header[] = { + 0x90, 0xC4, 0x00, 0x00, 0x29, keyno + }; + + uint8_t cmd[512] = {0}; + memcpy(cmd, cmd_header, sizeof(cmd_header)); + memcpy(&cmd[sizeof(cmd_header)], enc_keydata, 32); + memcpy(&cmd[sizeof(cmd_header) + 32], mact, 8); + int apdu_size = sizeof(cmd_header) + 32 + 8 + 1; + + + // ------- Actually send the APDU + const size_t RESPONSE_LENGTH = 8 + 2; + int outlen; + uint8_t resp[RESPONSE_LENGTH]; + int res = ExchangeAPDU14a(cmd, apdu_size, false, true, resp, RESPONSE_LENGTH, &outlen); + if(res != PM3_SUCCESS) + { + PrintAndLogEx(ERR, "Failed to send apdu"); + return res; + } + + if(outlen < 2) { + PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); + return PM3_ESOFT; + } + + if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) + { + PrintAndLogEx(ERR, "Failed to get file settings"); + return PM3_ESOFT; + } + + session_keys->command_counter++; // Should this be incremented only on success? + + return PM3_SUCCESS; + } static int CmdHF_ntag424_info(const char *Cmd) { @@ -169,27 +768,613 @@ static int CmdHF_ntag424_info(const char *Cmd) { arg_param_begin, arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, false); + CLIExecWithReturn(ctx, Cmd, argtable, true); CLIParserFree(ctx); PrintAndLogEx(INFO, "not implemented yet"); PrintAndLogEx(INFO, "Feel free to contribute!"); + return PM3_SUCCESS; +} + +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); + + if(keylen != 16) + { + PrintAndLogEx(ERR, "Key must be 16 bytes"); + return PM3_ESOFT; + } + + memcpy(key_out, key, 16); + + return PM3_SUCCESS; +} + +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"); + + 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}; + + if(ntag424_cli_get_auth_information(ctx, 1, 2, &keyno, key) != PM3_SUCCESS) + { + CLIParserFree(ctx); + return PM3_ESOFT; + } + + CLIParserFree(ctx); + + int res = SelectCard14443A_4(false, true, NULL); + if (res != PM3_SUCCESS) + { + PrintAndLogEx(ERR, "Failed to select card"); + DropField(); + return res; + } + + res = ntag424_select_application(); + if(res != PM3_SUCCESS) + { + DropField(); + return res; + } + + 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); + } + + DropField(); + + return res; +} + +// 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 2 -n 0 -k 00000000000000000000000000000000 -o 0 -l 256"); + + 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 read in file (default 0)"), + arg_int1("l", "length", "", "Number of bytes to read"), + 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) + { + PrintAndLogEx(INFO, "Reading unauthenticated"); + auth = 0; + } + else + { + PrintAndLogEx(INFO, "Reading authenticated"); + } + int offset = arg_get_int_def(ctx, 4, 0); + int read_length = arg_get_int(ctx, 5); + + CLIParserFree(ctx); - // has hardcoded application and three files. + 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; + } - /* - // 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..."); + if(auth) + { + res = ntag424_authenticate_ev2_first(keyno, key, NULL); + 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_file(fileno, offset, read_length, data); + 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_write(const char *Cmd) { + CLIParserContext *ctx; + 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"); + + 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_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) + { + PrintAndLogEx(INFO, "Will write unauthenticated"); + auth = 0; + } + else + { + PrintAndLogEx(INFO, "Will write authenticated"); + } + + int offset = arg_get_int_def(ctx, 4, 0); + + uint8_t data[512] = {0}; + int datalen = 512; + CLIGetHexWithReturn(ctx, 5, data, &datalen); + + 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; + } + + if(auth) + { + res = ntag424_authenticate_ev2_first(keyno, key, NULL); + 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_file(fileno, offset, datalen, data); + if(res != PM3_SUCCESS) + { + DropField(); + return res; + } + + PrintAndLogEx(SUCCESS, "Wrote %d bytes", datalen); + + DropField(); + + return res; +} + +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_read_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 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 SDMMetaRead, SDMFileRead, RFU and SDMCtrRet\n\n" + + "offset: A three byte offset to the data to be mirrored to/from. Can be specified multiple times.\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 offset1, then offset2, up to the number of offsets you want to write", + + + "hf ntag424 changefilesettings -f 2 -n 0 -k 00000000000000000000000000000000 -o 40 -a 00E0 -s C1 -c F000 --offset1 000020 --offset2 000043 --offset3 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, "offset1", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset2", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset3", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset4", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset5", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset6", "", "SDM offset (HEX 3 bytes)"), + arg_str0(NULL, "offset7", "", "SDM offset (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_offsets = 0; + uint8_t offsets[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_offsets++; + CLIGetHexWithReturn(ctx, 8+i, offsets[i], &len); + if(len != 3) + { + PrintAndLogEx(ERR, "Offset must be 3 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + } + else + { + break; + } + } + + + 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_read_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; + } + + 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_offsets; i++) + { + settings.optional_sdm_settings.offset[i][2] = offsets[i][0]; + settings.optional_sdm_settings.offset[i][1] = offsets[i][1]; + settings.optional_sdm_settings.offset[i][0] = offsets[i][2]; + } + + 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" + ); + 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 +1382,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..e4fab934d5 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" }, @@ -817,4 +822,4 @@ const static vocabulary_t vocabulary[] = { } #endif -#endif \ No newline at end of file +#endif From 011d1f6a4427412c18cb7f50ca86f2da21eb5d92 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Fri, 27 Oct 2023 06:52:30 +0200 Subject: [PATCH 02/10] Refactored MAC and IV calculations --- client/src/cmdhfntag424.c | 88 +++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index 725d2cdc35..c076bdafb4 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -251,6 +251,40 @@ static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *s return PM3_SUCCESS; } +static void ntag424_calc_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_mac(ntag424_session_keys_t *session_keys, uint8_t command, uint8_t command_header, 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], + command_header, + }; + + 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) + 32, mac); + + for(int i = 0; i < 8; i++) + { + out_mac[i] = mac[i*2+1]; + } + + free(mac_input); +} + // Write file settings is done with full communication mode. This can probably be broken out // and used for read/write of file when full communication mode is needed. static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t *settings, ntag424_session_keys_t *session_keys) { @@ -265,15 +299,8 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * // ------- Calculate IV - 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}; - uint8_t ivc[16] = {0}; - aes_encode(zero_iv, session_keys->encryption, iv_clear, ivc, 16); + uint8_t ivc[16]; + ntag424_calc_iv(session_keys, ivc); // ------- Encrypt file settings uint8_t padded_cmddata_buffer[256] = {0}; @@ -289,18 +316,8 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * aes_encode(ivc, session_keys->encryption, padded_cmddata_buffer, encrypted_cmddata, total_size); // ------- Calculate MAC - uint8_t mac_input_header[] = { 0x5f, - (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], - fileno // CmdHeader - }; - - uint8_t mac_input[256] = {0}; - memcpy(mac_input, mac_input_header, sizeof(mac_input_header)); - memcpy(&mac_input[sizeof(mac_input_header)], encrypted_cmddata, total_size); - uint8_t mac[16] = {0}; - mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + total_size, mac); - uint8_t mact[8] = { mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15] }; + uint8_t mact[8]; + ntag424_calc_mac(session_keys, 0x5f, fileno, encrypted_cmddata, total_size, mact); // ------- Assemble the actual command uint8_t lc = 1 + total_size + 8; // CmdHeader + size + mac*/ @@ -675,15 +692,8 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, // ------- Calculate IV - 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}; - uint8_t ive[16] = {0}; - aes_encode(zero_iv, session_keys->encryption, iv_clear, ive, 16); + uint8_t ive[16]; + ntag424_calc_iv(session_keys, ive); // ------- Calculate KeyData uint8_t keydata[32] = {0}; @@ -703,19 +713,8 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, aes_encode(ive, session_keys->encryption, keydata, enc_keydata, 32); // -------- Calculate MAC - uint8_t mac_input_header[] = { 0xC4, - (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], - keyno, - }; - - uint8_t mac_input[256] = {0}; - memcpy(mac_input, mac_input_header, sizeof(mac_input_header)); - memcpy(&mac_input[sizeof(mac_input_header)], enc_keydata, 32); - uint8_t mac[16] = {0}; - mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + 32, mac); - uint8_t mact[8] = { mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15] }; - + uint8_t mact[8]; + ntag424_calc_mac(session_keys, 0xC4, keyno, enc_keydata, 32, mact); // ------- Assemble APDU uint8_t cmd_header[] = { @@ -1290,7 +1289,8 @@ static int CmdHF_ntag424_changekey(const char *Cmd) { 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" + "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, From eb29e1a17e0a4dd5b0aed9b168e5b0d5dd6745d0 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Fri, 27 Oct 2023 07:18:13 +0200 Subject: [PATCH 03/10] Cleanup * Added some comments * Changed name of offsets to sdm_data since this is more apt. Not all of them are describing offsets * Fixed dumb bug in MAC refactor --- client/src/cmdhfntag424.c | 91 ++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index c076bdafb4..bb7f130a45 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -98,19 +98,19 @@ typedef struct { } ntag424_session_keys_t; // -------------- File settings structs ------------------------- -#define FILE_SETTINGS_OPTIONS_SDM_AND_MIRRORING (1 << 6) +// 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) -// RFU stuff... #define FILE_SETTINGS_SDM_OPTIONS_ENCODING_MODE_ASCII (1 << 0) typedef struct { uint8_t sdm_options; uint8_t sdm_access[2]; - uint8_t offset[8][3]; + uint8_t sdm_data[8][3]; } ntag424_file_sdm_settings_t; typedef struct { @@ -121,7 +121,7 @@ typedef struct { ntag424_file_sdm_settings_t optional_sdm_settings; } ntag424_file_settings_t; -#define SETTINGS_WITHOUT_OFFSETS_SIZE (1+1+2+3+1+2) +#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. @@ -143,11 +143,10 @@ typedef struct { // 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 rfu, uint8_t sdm_ctr_ret) + 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] = rfu << 4 | sdm_ctr_ret; + settings->optional_sdm_settings.sdm_access[0] = 0xf << 4 | sdm_ctr_ret; // (0xf is due to reserved for future use) }*/ @@ -160,11 +159,7 @@ static uint8_t ntag424_file_settings_get_sdm_file_read(const ntag424_file_settin } // Currently unused functions, commented out due to -Wunused-function -/*static uint8_t ntag424_file_settings_get_sdm_rfu(const ntag424_file_settings_t *settings) { - return settings->optional_sdm_settings.sdm_access[0] >> 4; -} - -static uint8_t ntag424_file_settings_get_sdm_ctr_ret(const ntag424_file_settings_t *settings) { +/*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; }*/ @@ -233,7 +228,7 @@ static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *s } if(outlen < 9) { - PrintAndLogEx(ERR, "Incorrect response asdaslength: %d", outlen); + PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); return PM3_ESOFT; } @@ -275,7 +270,7 @@ static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t comma 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) + 32, mac); + mbedtls_aes_cmac_prf_128(session_keys->mac, 16, mac_input, sizeof(mac_input_header) + datalen, mac); for(int i = 0; i < 8; i++) { @@ -349,7 +344,7 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * } if(outlen != RESPONSE_LENGTH) { - PrintAndLogEx(ERR, "Incorrect response asdaslength: %d, %02X%02X", outlen, resp[outlen-2], resp[outlen-1]); + PrintAndLogEx(ERR, "Incorrect response length: %d, %02X%02X", outlen, resp[outlen-2], resp[outlen-1]); return PM3_ESOFT; } @@ -366,7 +361,7 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * static void ntag424_print_file_settings(uint8_t fileno, const ntag424_file_settings_t *settings) { - int num_offsets = (ntag424_calc_file_settings_size(settings) - SETTINGS_WITHOUT_OFFSETS_SIZE) / 3; + int num_sdm_data = (ntag424_calc_file_settings_size(settings) - SETTINGS_WITHOUT_SDM_DATA_SIZE) / 3; PrintAndLogEx(SUCCESS, "--- " _CYAN_("File %d settings:") , fileno); @@ -381,21 +376,18 @@ static void ntag424_print_file_settings(uint8_t fileno, const ntag424_file_setti 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_offsets > 0) + if(num_sdm_data > 0) { - PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM offsets: ")); - for(int i = 0; i < num_offsets; i++) + PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM data: ")); + for(int i = 0; i < num_sdm_data; i++) { - PrintAndLogEx(SUCCESS, " offset %d: %02X%02X%02X", i, - settings->optional_sdm_settings.offset[i][2], - settings->optional_sdm_settings.offset[i][1], - settings->optional_sdm_settings.offset[i][0]); + 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]); } } } - - - // Add printing of variable number of offsets here... } // NTAG424 only have one static application, so we select it here @@ -1089,15 +1081,16 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { "sdmaccess: two byte access rights.\n" "Each nibble is a key, or E for plain mirror and F for no mirroring\n" - "Order is SDMMetaRead, SDMFileRead, RFU and SDMCtrRet\n\n" + "Order is SDMMetaRead, SDMFileRead, Reserved and SDMCtrRet\n\n" - "offset: A three byte offset to the data to be mirrored to/from. Can be specified multiple times.\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 offset1, then offset2, up to the number of offsets you want to write", + "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 --offset1 000020 --offset2 000043 --offset3 000043"); + "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, @@ -1108,13 +1101,14 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { 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, "offset1", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset2", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset3", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset4", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset5", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset6", "", "SDM offset (HEX 3 bytes)"), - arg_str0(NULL, "offset7", "", "SDM offset (HEX 3 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 }; @@ -1132,9 +1126,8 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { uint8_t sdmoptions[1]; uint8_t has_sdmaccess = 0; uint8_t sdmaccess[2]; - uint8_t num_offsets = 0; - uint8_t offsets[8][3]; - + 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) { @@ -1161,7 +1154,7 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { CLIGetHexWithReturn(ctx, 5, access, &len); if(len != 2) { - PrintAndLogEx(ERR, "access must be 2 bytes"); + PrintAndLogEx(ERR, "Access must be 2 bytes"); CLIParserFree(ctx); return PM3_EINVARG; } @@ -1185,7 +1178,7 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { CLIGetHexWithReturn(ctx, 7, sdmaccess, &len); if(len != 2) { - PrintAndLogEx(ERR, "SDM access must be 2 bytes"); + PrintAndLogEx(ERR, "SDM Access must be 2 bytes"); CLIParserFree(ctx); return PM3_EINVARG; } @@ -1196,11 +1189,11 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { if(arg_get_str(ctx,8+i)->count == 1) { len = 3; - num_offsets++; - CLIGetHexWithReturn(ctx, 8+i, offsets[i], &len); + num_sdm_data++; + CLIGetHexWithReturn(ctx, 8+i, sdm_data[i], &len); if(len != 3) { - PrintAndLogEx(ERR, "Offset must be 3 bytes"); + PrintAndLogEx(ERR, "sdmdata must be 3 bytes"); CLIParserFree(ctx); return PM3_EINVARG; } @@ -1263,11 +1256,11 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { memcpy(settings.optional_sdm_settings.sdm_access, sdmaccess, 2); } - for(int i = 0; i < num_offsets; i++) + for(int i = 0; i < num_sdm_data; i++) { - settings.optional_sdm_settings.offset[i][2] = offsets[i][0]; - settings.optional_sdm_settings.offset[i][1] = offsets[i][1]; - settings.optional_sdm_settings.offset[i][0] = offsets[i][2]; + 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]; } if(ntag424_write_file_settings(fileno, &settings, &session) != PM3_SUCCESS) From ad875a8c01b3671acd04759e932fa79ee97ad424 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Fri, 27 Oct 2023 16:43:58 +0200 Subject: [PATCH 04/10] Refactored communication encryption --- client/src/cmdhfntag424.c | 124 ++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index bb7f130a45..e74182262e 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -28,6 +28,7 @@ #include "crypto/libpcrypto.h" // aes_decode #include "cmac.h" #include "cmdhf14a.h" +#include "ui.h" #include "util.h" #include "crc32.h" @@ -212,6 +213,10 @@ static int ntag424_calc_file_settings_size(const ntag424_file_settings_t *settin 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 int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *settings_out) { const size_t RESPONSE_LENGTH = sizeof(ntag424_file_settings_t) + 2; @@ -280,8 +285,52 @@ static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t comma free(mac_input); } -// Write file settings is done with full communication mode. This can probably be broken out -// and used for read/write of file when full communication mode is needed. +static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_out, int *apdu_out_size, ntag424_session_keys_t *session_keys) +{ +#define MAC_SIZE 8 +#define APDU_HEADER_SIZE 5 +#define APDU_OVERHEAD (APDU_HEADER_SIZE + 1) + + // ------- Calculate IV + uint8_t ivc[16]; + ntag424_calc_iv(session_keys, ivc); + + + // ------- Copy apdu header + size_t size = apdu_in[4]; + memcpy(apdu_out, apdu_in, 6); + + size_t encrypt_data_size = size - 1; + 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}; + + int apdu_final_size = APDU_OVERHEAD + padded_data_size + 8 + 1; // + MAC and CmdHdr + if(*apdu_out_size < apdu_final_size) + { + PrintAndLogEx(ERR, "APDU out buffer not large enough"); + return PM3_EINVARG; + } + + *apdu_out_size = apdu_final_size; + + // ------ Pad data + memcpy(temp_buffer, &apdu_in[APDU_HEADER_SIZE + 1], encrypt_data_size); // We encrypt everything except the CmdHdr + temp_buffer[encrypt_data_size] = 0x80; + + // ------ Encrypt it + memcpy(apdu_out, apdu_in, 4); + aes_encode(ivc, session_keys->encryption, temp_buffer, &apdu_out[6], padded_data_size); + + // ------ Add MAC + ntag424_calc_mac(session_keys, apdu_in[1], apdu_in[5], &apdu_out[6], padded_data_size, &apdu_out[APDU_HEADER_SIZE + padded_data_size + 1]); + + apdu_out[4] = (uint8_t)(padded_data_size+8+1); // Set size to CmdHdr + padded data + MAC + apdu_out[APDU_HEADER_SIZE + padded_data_size + 8 + 1] = 0; // Le + + + 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 @@ -292,51 +341,29 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * .optional_sdm_settings = settings->optional_sdm_settings, }; - - // ------- Calculate IV - uint8_t ivc[16]; - ntag424_calc_iv(session_keys, ivc); - - // ------- Encrypt file settings - uint8_t padded_cmddata_buffer[256] = {0}; - uint8_t encrypted_cmddata[256] = {0}; - size_t settings_size = ntag424_calc_file_settings_size(settings) - 4; // This is weird, but since the write settings are the same as - // the settings read out, but minus file type and file size, we subtract 4 here. - - size_t total_size = settings_size + 16 - (settings_size % 16); // pad up to 16 byte blocks - memcpy(padded_cmddata_buffer, (void*)&write_settings, settings_size); - if(total_size > settings_size) { - padded_cmddata_buffer[settings_size] = 0x80; - } - aes_encode(ivc, session_keys->encryption, padded_cmddata_buffer, encrypted_cmddata, total_size); - - // ------- Calculate MAC - uint8_t mact[8]; - ntag424_calc_mac(session_keys, 0x5f, fileno, encrypted_cmddata, total_size, mact); - // ------- Assemble the actual command - uint8_t lc = 1 + total_size + 8; // CmdHeader + size + mac*/ + size_t settings_size = ntag424_calc_file_write_settings_size(settings); + uint8_t lc = 1 + settings_size; // CmdHeader + size */ uint8_t cmd_header[] = { 0x90, 0x5f, 0x00, 0x00, lc, fileno }; - uint8_t cmd[256] = {0}; - memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], encrypted_cmddata, total_size); - memcpy(&cmd[sizeof(cmd_header) + total_size], mact, sizeof(mact)); - cmd[sizeof(cmd_header) + total_size + sizeof(mact)] = 0x00; + memcpy(&cmd[sizeof(cmd_header)], (void*)&write_settings, settings_size); + cmd[sizeof(cmd_header) + settings_size] = 0x00; - size_t apdu_size = sizeof(cmd_header) + total_size + sizeof(mact) + 1; + uint8_t apdu_out[256] = {0}; + int apdu_out_size = 256; + ntag424_comm_full_encrypt_apdu(cmd, apdu_out, &apdu_out_size, session_keys); // ------- Actually send the APDU const size_t RESPONSE_LENGTH = 8 + 2; int outlen; uint8_t resp[RESPONSE_LENGTH]; - int res = ExchangeAPDU14a(cmd, apdu_size, false, true, resp, RESPONSE_LENGTH, &outlen); + int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); if(res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); @@ -355,7 +382,6 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * } session_keys->command_counter++; // Should this be incremented only on success? - return PM3_SUCCESS; } @@ -681,50 +707,43 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, { memcpy(key, new_key, 16); } - - - // ------- Calculate IV - uint8_t ive[16]; - ntag424_calc_iv(session_keys, ive); - + // ------- Calculate KeyData uint8_t keydata[32] = {0}; memcpy(keydata, key, 16); keydata[16] = version; + int key_data_len; if(keyno != 0) { memcpy(&keydata[17], crc, 4); keydata[21] = 0x80; + key_data_len = 16 + 4 + 1; } else { keydata[17] = 0x80; + key_data_len = 16 + 1; } - uint8_t enc_keydata[32] = {0}; - aes_encode(ive, session_keys->encryption, keydata, enc_keydata, 32); - - // -------- Calculate MAC - uint8_t mact[8]; - ntag424_calc_mac(session_keys, 0xC4, keyno, enc_keydata, 32, mact); - // ------- Assemble APDU uint8_t cmd_header[] = { - 0x90, 0xC4, 0x00, 0x00, 0x29, keyno + 0x90, 0xC4, 0x00, 0x00, key_data_len+1, keyno }; uint8_t cmd[512] = {0}; memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], enc_keydata, 32); - memcpy(&cmd[sizeof(cmd_header) + 32], mact, 8); - int apdu_size = sizeof(cmd_header) + 32 + 8 + 1; + memcpy(&cmd[sizeof(cmd_header)], keydata, key_data_len); + + uint8_t apdu_out[256]; + int apdu_out_size = 256; + ntag424_comm_full_encrypt_apdu(cmd, apdu_out, &apdu_out_size, session_keys); // ------- Actually send the APDU const size_t RESPONSE_LENGTH = 8 + 2; int outlen; uint8_t resp[RESPONSE_LENGTH]; - int res = ExchangeAPDU14a(cmd, apdu_size, false, true, resp, RESPONSE_LENGTH, &outlen); + int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); if(res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); @@ -738,7 +757,7 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) { - PrintAndLogEx(ERR, "Failed to get file settings"); + PrintAndLogEx(ERR, "Error when changing key. Wrong old key?"); return PM3_ESOFT; } @@ -761,7 +780,6 @@ static int CmdHF_ntag424_info(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, true); CLIParserFree(ctx); - PrintAndLogEx(INFO, "not implemented yet"); PrintAndLogEx(INFO, "Feel free to contribute!"); return PM3_SUCCESS; From 05a230246686bfbed8f5c5a119d0d7942455f315 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Fri, 27 Oct 2023 18:09:38 +0200 Subject: [PATCH 05/10] Implemented style --- client/src/cmdhfntag424.c | 508 +++++++++++++------------------- client/src/pm3line_vocabulary.h | 2 +- doc/commands.json | 115 +++++++- doc/commands.md | 9 +- 4 files changed, 312 insertions(+), 322 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index e74182262e..25e862ef8c 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -100,7 +100,7 @@ typedef struct { // -------------- 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_OPTIONS_SDM_AND_MIRRORING (1 << 6) #define FILE_SETTINGS_SDM_OPTIONS_UID (1 << 7) #define FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER (1 << 6) @@ -168,47 +168,43 @@ static uint8_t ntag424_file_settings_get_sdm_file_read(const ntag424_file_settin // 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) { + + 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) - { + 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) - { + 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) { + if (ntag424_file_settings_get_sdm_meta_read(settings) <= 0x04) { size += 3; // PICCDataOffset } - if(ntag424_file_settings_get_sdm_file_read(settings) != 0x0f) { + 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) - { + 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) { + 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) - { + if (settings->optional_sdm_settings.sdm_options & FILE_SETTINGS_SDM_OPTIONS_SDM_READ_COUNTER_LIMIT) { size += 3; // SDMReadCtrLimit - } + } } return size; @@ -217,7 +213,7 @@ static int ntag424_calc_file_settings_size(const ntag424_file_settings_t *settin static int ntag424_calc_file_write_settings_size(const ntag424_file_settings_t *settings) { return ntag424_calc_file_settings_size(settings) - 4; } - + static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *settings_out) { const size_t RESPONSE_LENGTH = sizeof(ntag424_file_settings_t) + 2; uint8_t cmd[] = { 0x90, 0xF5, 0x00, 0x00, 0x01, fileno, 0x00}; @@ -226,26 +222,23 @@ static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *s int res; res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen < 9) { + if (outlen < 9) { PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); return PM3_ESOFT; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Failed to get file settings"); return PM3_ESOFT; } - if(settings_out) - { - memcpy(settings_out, resp, outlen-2); + if (settings_out) { + memcpy(settings_out, resp, outlen - 2); } return PM3_SUCCESS; @@ -253,10 +246,10 @@ static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *s static void ntag424_calc_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 - }; + 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); @@ -264,33 +257,31 @@ static void ntag424_calc_iv(ntag424_session_keys_t *session_keys, uint8_t *out_i static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t command, uint8_t command_header, 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], - command_header, - }; + (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], + command_header, + }; int mac_input_len = sizeof(mac_input_header) + datalen; - uint8_t *mac_input = (uint8_t*)malloc(mac_input_len); + 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]; + + for (int i = 0; i < 8; i++) { + out_mac[i] = mac[i * 2 + 1]; } free(mac_input); } -static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_out, int *apdu_out_size, ntag424_session_keys_t *session_keys) -{ +static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_out, int *apdu_out_size, ntag424_session_keys_t *session_keys) { #define MAC_SIZE 8 #define APDU_HEADER_SIZE 5 #define APDU_OVERHEAD (APDU_HEADER_SIZE + 1) - + // ------- Calculate IV uint8_t ivc[16]; ntag424_calc_iv(session_keys, ivc); @@ -305,8 +296,7 @@ static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_ uint8_t temp_buffer[256] = {0}; int apdu_final_size = APDU_OVERHEAD + padded_data_size + 8 + 1; // + MAC and CmdHdr - if(*apdu_out_size < apdu_final_size) - { + if (*apdu_out_size < apdu_final_size) { PrintAndLogEx(ERR, "APDU out buffer not large enough"); return PM3_EINVARG; } @@ -324,15 +314,15 @@ static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_ // ------ Add MAC ntag424_calc_mac(session_keys, apdu_in[1], apdu_in[5], &apdu_out[6], padded_data_size, &apdu_out[APDU_HEADER_SIZE + padded_data_size + 1]); - apdu_out[4] = (uint8_t)(padded_data_size+8+1); // Set size to CmdHdr + padded data + MAC + apdu_out[4] = (uint8_t)(padded_data_size + 8 + 1); // Set size to CmdHdr + padded data + MAC apdu_out[APDU_HEADER_SIZE + padded_data_size + 8 + 1] = 0; // Le - + 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, @@ -352,7 +342,7 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * }; uint8_t cmd[256] = {0}; memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], (void*)&write_settings, settings_size); + memcpy(&cmd[sizeof(cmd_header)], (void *)&write_settings, settings_size); cmd[sizeof(cmd_header) + settings_size] = 0x00; uint8_t apdu_out[256] = {0}; @@ -364,19 +354,17 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * int outlen; uint8_t resp[RESPONSE_LENGTH]; int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen != RESPONSE_LENGTH) { - PrintAndLogEx(ERR, "Incorrect response length: %d, %02X%02X", outlen, resp[outlen-2], resp[outlen-1]); + if (outlen != RESPONSE_LENGTH) { + PrintAndLogEx(ERR, "Incorrect response length: %d, %02X%02X", outlen, resp[outlen - 2], resp[outlen - 1]); return PM3_ESOFT; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Failed to get file settings"); return PM3_ESOFT; } @@ -388,25 +376,22 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * 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, "--- " _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) - { + 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) - { + if (num_sdm_data > 0) { PrintAndLogEx(SUCCESS, "--- " _CYAN_("SDM data: ")); - for(int i = 0; i < num_sdm_data; i++) - { + 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], @@ -425,14 +410,12 @@ static int ntag424_select_application(void) { int res; res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + 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) - { + if (outlen != RESPONSE_LENGTH || resp[RESPONSE_LENGTH - 2] != 0x90 || resp[RESPONSE_LENGTH - 1] != 0x00) { PrintAndLogEx(ERR, "Failed to select application"); return PM3_ESOFT; } @@ -440,8 +423,7 @@ static int ntag424_select_application(void) { return PM3_SUCCESS; } -static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) -{ +static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) { const size_t RESPONSE_LENGTH = 16 + 2; uint8_t cmd[] = {0x90, 0x71, 0x00, 0x00, 0x02, keyno, 0x00, 0x00}; uint8_t resp[RESPONSE_LENGTH]; @@ -449,14 +431,12 @@ static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) int res; res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen != RESPONSE_LENGTH || resp[RESPONSE_LENGTH-2] != 0x91 || resp[RESPONSE_LENGTH-1] != 0xAF) - { + if (outlen != RESPONSE_LENGTH || resp[RESPONSE_LENGTH - 2] != 0x91 || resp[RESPONSE_LENGTH - 1] != 0xAF) { PrintAndLogEx(ERR, "Failed to get RndB (invalid key number?)"); return PM3_ESOFT; } @@ -467,8 +447,7 @@ static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) return PM3_SUCCESS; } -static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response) -{ +static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response) { uint8_t cmd_header[] = { 0x90, 0xAF, 0x00, 0x00, 0x20 }; uint8_t cmd[sizeof(cmd_header) + 32 + 1] = {0}; @@ -482,31 +461,27 @@ static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response) int res; res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { return res; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Challenge failed: wrong key?"); return PM3_ESOFT; } - memcpy(response, resp, outlen-2); + memcpy(response, resp, outlen - 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 +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) - { + if (res != PM3_SUCCESS) { return res; } @@ -528,20 +503,18 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s 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]; + uint8_t resp[4 + 16 + 6 + 6]; res = ntag424_auth_second_step(concat_enc, resp); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { return res; } ntag424_ev2_response_t response; - aes_decode(iv, key, resp, (uint8_t*)&response, sizeof(ntag424_ev2_response_t)); + 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]) - { + 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" @@ -551,8 +524,7 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s } // -------- Optionally calculate session keys - if(session_keys_out) - { + if (session_keys_out) { memset(session_keys_out, 0, sizeof(ntag424_session_keys_t)); memcpy(session_keys_out->ti, response.ti, sizeof(response.ti)); @@ -562,17 +534,17 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s // [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] - }; + 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] || @@ -580,18 +552,18 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s // [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] - }; - + 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); } @@ -607,17 +579,16 @@ static int ntag424_write_file(uint8_t fileno, uint16_t offset, uint16_t num_byte const size_t RESPONSE_LENGTH = 2; size_t remainder = 0; - if(num_bytes > MAX_WRITE_APDU) - { + if (num_bytes > MAX_WRITE_APDU) { remainder = num_bytes - MAX_WRITE_APDU; num_bytes = MAX_WRITE_APDU; } - // 248 + + // 248 + uint8_t cmd_header[] = { 0x90, 0x8d, 0x00, 0x00, 0x07 + num_bytes, 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)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[512] = {0}; @@ -625,34 +596,31 @@ static int ntag424_write_file(uint8_t fileno, uint16_t offset, uint16_t num_byte memcpy(&cmd[sizeof(cmd_header)], in, num_bytes); size_t total_size = sizeof(cmd_header) + num_bytes + 1; //(Le) - + uint8_t resp[RESPONSE_LENGTH]; int outlen = 0; int res; res = ExchangeAPDU14a(cmd, total_size, false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen != RESPONSE_LENGTH) { + if (outlen != RESPONSE_LENGTH) { PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); return PM3_ESOFT; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Failed to write file"); return PM3_ESOFT; } - if(remainder > 0) - { + if (remainder > 0) { return ntag424_write_file(fileno, offset + num_bytes, remainder, &in[num_bytes]); } - + return PM3_SUCCESS; } @@ -660,29 +628,28 @@ static int ntag424_write_file(uint8_t fileno, uint16_t offset, uint16_t num_byte // first unless file has free read access. static int ntag424_read_file(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *out) { const size_t RESPONSE_LENGTH = num_bytes + 2; - + uint8_t cmd[] = { 0x90, 0xad, 0x00, 0x00, 0x07, fileno, - (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset - (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00, //size - 0x00 }; + (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset + (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00, //size + 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) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen != RESPONSE_LENGTH) { + if (outlen != RESPONSE_LENGTH) { PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); return PM3_ESOFT; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Failed to read file"); return PM3_ESOFT; } @@ -695,39 +662,32 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, // -------- Calculate xor and crc uint8_t key[16] = {0}; uint8_t crc[4] = {0}; - if(keyno != 0) - { - for(int i = 0; i < 16; i++) - { + 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 - { + } else { memcpy(key, new_key, 16); } - + // ------- Calculate KeyData uint8_t keydata[32] = {0}; memcpy(keydata, key, 16); keydata[16] = version; int key_data_len; - if(keyno != 0) - { + if (keyno != 0) { memcpy(&keydata[17], crc, 4); keydata[21] = 0x80; key_data_len = 16 + 4 + 1; - } - else - { + } else { keydata[17] = 0x80; key_data_len = 16 + 1; } // ------- Assemble APDU uint8_t cmd_header[] = { - 0x90, 0xC4, 0x00, 0x00, key_data_len+1, keyno + 0x90, 0xC4, 0x00, 0x00, key_data_len + 1, keyno }; uint8_t cmd[512] = {0}; @@ -737,32 +697,30 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, uint8_t apdu_out[256]; int apdu_out_size = 256; ntag424_comm_full_encrypt_apdu(cmd, apdu_out, &apdu_out_size, session_keys); - + // ------- Actually send the APDU const size_t RESPONSE_LENGTH = 8 + 2; int outlen; uint8_t resp[RESPONSE_LENGTH]; int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if(outlen < 2) { + if (outlen < 2) { PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); return PM3_ESOFT; } - if(resp[outlen-2] != 0x91 || resp[outlen-1] != 0x00) - { + if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { PrintAndLogEx(ERR, "Error when changing key. Wrong old key?"); return PM3_ESOFT; } session_keys->command_counter++; // Should this be incremented only on success? - + return PM3_SUCCESS; } @@ -785,24 +743,21 @@ static int CmdHF_ntag424_info(const char *Cmd) { return PM3_SUCCESS; } -static int ntag424_cli_get_auth_information(CLIParserContext *ctx, int key_no_index, int key_index, int *keyno, uint8_t *key_out) -{ +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) - { + if (keyno) { *keyno = arg_get_int(ctx, key_no_index); } CLIGetHexWithReturn(ctx, key_index, key, &keylen); - if(keylen != 16) - { + if (keylen != 16) { PrintAndLogEx(ERR, "Key must be 16 bytes"); return PM3_ESOFT; } memcpy(key_out, key, 16); - + return PM3_SUCCESS; } @@ -822,8 +777,7 @@ static int CmdHF_ntag424_auth(const char *Cmd) { int keyno; uint8_t key[16] = {0}; - if(ntag424_cli_get_auth_information(ctx, 1, 2, &keyno, key) != PM3_SUCCESS) - { + if (ntag424_cli_get_auth_information(ctx, 1, 2, &keyno, key) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_ESOFT; } @@ -831,27 +785,22 @@ static int CmdHF_ntag424_auth(const char *Cmd) { CLIParserFree(ctx); int res = SelectCard14443A_4(false, true, NULL); - if (res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to select card"); DropField(); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; - } + } res = ntag424_authenticate_ev2_first(keyno, key, NULL); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); - } - else - { + } else { PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); } @@ -884,47 +833,38 @@ static int CmdHF_ntag424_read(const char *Cmd) { int fileno = arg_get_int(ctx, 1); - if(ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) - { + if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { PrintAndLogEx(INFO, "Reading unauthenticated"); auth = 0; - } - else - { + } else { PrintAndLogEx(INFO, "Reading authenticated"); } 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) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to select card"); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; - } + } - if(auth) - { + if (auth) { res = ntag424_authenticate_ev2_first(keyno, key, NULL); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); DropField(); return res; - } - else - { + } else { PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); } } @@ -932,8 +872,7 @@ static int CmdHF_ntag424_read(const char *Cmd) { uint8_t data[512]; res = ntag424_read_file(fileno, offset, read_length, data); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; } @@ -969,13 +908,10 @@ static int CmdHF_ntag424_write(const char *Cmd) { int fileno = arg_get_int(ctx, 1); - if(ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) - { + if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { PrintAndLogEx(INFO, "Will write unauthenticated"); auth = 0; - } - else - { + } else { PrintAndLogEx(INFO, "Will write authenticated"); } @@ -984,42 +920,35 @@ static int CmdHF_ntag424_write(const char *Cmd) { uint8_t data[512] = {0}; int datalen = 512; CLIGetHexWithReturn(ctx, 5, data, &datalen); - + CLIParserFree(ctx); int res = SelectCard14443A_4(false, true, NULL); - if (res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to select card"); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; - } + } - if(auth) - { + if (auth) { res = ntag424_authenticate_ev2_first(keyno, key, NULL); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); DropField(); return res; - } - else - { + } else { PrintAndLogEx(SUCCESS, "Successfully authenticated with key %d", keyno); } } res = ntag424_write_file(fileno, offset, datalen, data); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; } @@ -1048,25 +977,22 @@ static int CmdHF_ntag424_getfilesettings(const char *Cmd) { CLIParserFree(ctx); int res = SelectCard14443A_4(false, true, NULL); - if (res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to select card"); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; - } + } ntag424_file_settings_t settings; res = ntag424_read_file_settings(fileno, &settings); DropField(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { return res; } @@ -1084,11 +1010,11 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { "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" @@ -1096,7 +1022,7 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { " 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 SDMMetaRead, SDMFileRead, Reserved and SDMCtrRet\n\n" @@ -1106,8 +1032,8 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { "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[] = { @@ -1147,149 +1073,125 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { 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) - { + 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) - { + if (arg_get_str(ctx, 4)->count == 1) { has_options = 1; CLIGetHexWithReturn(ctx, 4, options, &len); - if(len != 1) - { + 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) - { + if (arg_get_str(ctx, 5)->count == 1) { has_access = 1; CLIGetHexWithReturn(ctx, 5, access, &len); - if(len != 2) - { + 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) - { + if (arg_get_str(ctx, 6)->count == 1) { has_sdmoptions = 1; CLIGetHexWithReturn(ctx, 6, sdmoptions, &len); - if(len != 1) - { + 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) - { + if (arg_get_str(ctx, 7)->count == 1) { has_sdmaccess = 1; CLIGetHexWithReturn(ctx, 7, sdmaccess, &len); - if(len != 2) - { + 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) - { + 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) - { + CLIGetHexWithReturn(ctx, 8 + i, sdm_data[i], &len); + if (len != 3) { PrintAndLogEx(ERR, "sdmdata must be 3 bytes"); CLIParserFree(ctx); return PM3_EINVARG; } - } - else - { + } else { break; } } - + CLIParserFree(ctx); int res = SelectCard14443A_4(false, true, NULL); - if (res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to select card"); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); return res; - } + } ntag424_file_settings_t settings; res = ntag424_read_file_settings(fileno, &settings); - if(res != PM3_SUCCESS) - { + 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) - { + if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); DropField(); return res; } - if(has_options) - { + if (has_options) { settings.options = options[0]; } - if(has_access) - { + if (has_access) { memcpy(settings.access, access, 2); } - if(has_sdmoptions) - { + if (has_sdmoptions) { settings.optional_sdm_settings.sdm_options = sdmoptions[0]; } - if(has_sdmaccess) - { + if (has_sdmaccess) { memcpy(settings.optional_sdm_settings.sdm_access, sdmaccess, 2); } - for(int i = 0; i < num_sdm_data; i++) - { + 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]; } - if(ntag424_write_file_settings(fileno, &settings, &session) != PM3_SUCCESS) - { + 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; } @@ -1320,24 +1222,20 @@ static int CmdHF_ntag424_changekey(const char *Cmd) { uint8_t newkey[16]; uint8_t authkey[16]; - if(keyno != 0) - { - if(ntag424_cli_get_auth_information(ctx, 0, 2, NULL, oldkey) != PM3_SUCCESS) - { - + 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) - { + 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) - { + 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; @@ -1345,43 +1243,35 @@ static int CmdHF_ntag424_changekey(const char *Cmd) { CLIParserFree(ctx); int res = SelectCard14443A_4(false, true, NULL); - if (res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to select card"); return res; } res = ntag424_select_application(); - if(res != PM3_SUCCESS) - { + 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) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to authenticate"); return PM3_ESOFT; - } - else - { + } else { PrintAndLogEx(SUCCESS, "Successfully authenticated"); } - + res = ntag424_change_key(keyno, newkey, oldkey, version, &session); - if(res != PM3_SUCCESS) - { + if (res != PM3_SUCCESS) { DropField(); PrintAndLogEx(ERR, "Failed to change key"); DropField(); return PM3_ESOFT; - } - else - { + } else { PrintAndLogEx(SUCCESS, "Successfully changed key %d", keyno); } diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index e4fab934d5..5b3720d85e 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -822,4 +822,4 @@ const static vocabulary_t vocabulary[] = { } #endif -#endif +#endif \ No newline at end of file diff --git a/doc/commands.json b/doc/commands.json index 6355595266..d20870949d 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 SDMMetaRead, SDMFileRead, Reserved and SDMCtrRet 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,22 @@ ], "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" ], - "usage": "hf ntag424 sdm [-h]" + "usage": "hf ntag424 read [-h] -f [-n ] [-k ] [-o ] -l " }, "hf ntag424 view": { "command": "hf ntag424 view", @@ -6828,6 +6905,23 @@ ], "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" + ], + "usage": "hf ntag424 write [-h] -f [-n ] [-k ] [-o ] -d " + }, "hf plot": { "command": "hf plot", "description": "Plots HF signal after RF signal path and A/D conversion.", @@ -8053,9 +8147,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 +12010,8 @@ } }, "metadata": { - "commands_extracted": 691, + "commands_extracted": 696, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2023-10-22T12:20:10" + "extracted_on": "2023-10-27T16:08:32" } } \ 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 From 1eaf8d0e804f6bd02c74617639446849554c6277 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Sat, 28 Oct 2023 10:33:30 +0200 Subject: [PATCH 06/10] Fixed incorrect order of sdm access rights in command help --- client/src/cmdhfntag424.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index 25e862ef8c..d0f8022e91 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -1025,7 +1025,7 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { "sdmaccess: two byte access rights.\n" "Each nibble is a key, or E for plain mirror and F for no mirroring\n" - "Order is SDMMetaRead, SDMFileRead, Reserved and SDMCtrRet\n\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" From 6d290f1bae8b34a6303dec1bd3d8876ab13580c2 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Sun, 29 Oct 2023 13:43:25 +0100 Subject: [PATCH 07/10] Less magic numbers, more refactoring --- client/src/cmdhfntag424.c | 425 ++++++++++++++++++++------------------ doc/commands.json | 4 +- 2 files changed, 223 insertions(+), 206 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index d0f8022e91..f01df9605e 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -21,6 +21,7 @@ #include "cmdparser.h" #include "commonutil.h" #include "comms.h" +#include "iso7816/apduinfo.h" #include "protocols.h" #include "cliparser.h" #include "cmdmain.h" @@ -32,7 +33,17 @@ #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 static int CmdHelp(const char *Cmd); @@ -98,6 +109,12 @@ typedef struct { uint8_t mac[16]; } ntag424_session_keys_t; +typedef enum { + COMM_PLAIN, + COMM_MAC, + COMM_FULL +} ntag424_communication_mode_t; + // -------------- 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) @@ -214,36 +231,6 @@ static int ntag424_calc_file_write_settings_size(const ntag424_file_settings_t * return ntag424_calc_file_settings_size(settings) - 4; } -static int ntag424_read_file_settings(uint8_t fileno, ntag424_file_settings_t *settings_out) { - const size_t RESPONSE_LENGTH = sizeof(ntag424_file_settings_t) + 2; - uint8_t cmd[] = { 0x90, 0xF5, 0x00, 0x00, 0x01, fileno, 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 < 9) { - PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); - return PM3_ESOFT; - } - - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Failed to get file settings"); - return PM3_ESOFT; - } - - if (settings_out) { - memcpy(settings_out, resp, outlen - 2); - } - - return PM3_SUCCESS; -} - static void ntag424_calc_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], @@ -277,46 +264,132 @@ static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t comma free(mac_input); } -static int ntag424_comm_full_encrypt_apdu(const uint8_t *apdu_in, uint8_t *apdu_out, int *apdu_out_size, ntag424_session_keys_t *session_keys) { -#define MAC_SIZE 8 -#define APDU_HEADER_SIZE 5 -#define APDU_OVERHEAD (APDU_HEADER_SIZE + 1) +static int ntag424_comm_mac_apdu(APDU_t *apdu, 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[0], &apdu->data[1], size - 1, &apdu->data[size]); + + apdu->lc = size + 8; + + return PM3_SUCCESS; +} +static int ntag424_comm_encrypt_apdu(APDU_t *apdu, int apdu_max_data_size, ntag424_session_keys_t *session_keys) { // ------- Calculate IV uint8_t ivc[16]; ntag424_calc_iv(session_keys, ivc); - - // ------- Copy apdu header - size_t size = apdu_in[4]; - memcpy(apdu_out, apdu_in, 6); + int size = apdu->lc; size_t encrypt_data_size = size - 1; 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}; - int apdu_final_size = APDU_OVERHEAD + padded_data_size + 8 + 1; // + MAC and CmdHdr - if (*apdu_out_size < apdu_final_size) { - PrintAndLogEx(ERR, "APDU out buffer not large enough"); - return PM3_EINVARG; + if (padded_data_size + 1 > apdu_max_data_size) { + return PM3_EOVFLOW; } - *apdu_out_size = apdu_final_size; - // ------ Pad data - memcpy(temp_buffer, &apdu_in[APDU_HEADER_SIZE + 1], encrypt_data_size); // We encrypt everything except the CmdHdr + memcpy(temp_buffer, &apdu->data[1], encrypt_data_size); // We encrypt everything except the CmdHdr (first byte in data) temp_buffer[encrypt_data_size] = 0x80; // ------ Encrypt it - memcpy(apdu_out, apdu_in, 4); - aes_encode(ivc, session_keys->encryption, temp_buffer, &apdu_out[6], padded_data_size); + aes_encode(ivc, session_keys->encryption, temp_buffer, &apdu->data[1], padded_data_size); + + apdu->lc = (uint8_t)(1 + padded_data_size); // Set size to CmdHdr + padded data + + return PM3_SUCCESS; +} - // ------ Add MAC - ntag424_calc_mac(session_keys, apdu_in[1], apdu_in[5], &apdu_out[6], padded_data_size, &apdu_out[APDU_HEADER_SIZE + padded_data_size + 1]); +static int ntag424_exchange_apdu(APDU_t *apdu, 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) { - apdu_out[4] = (uint8_t)(padded_data_size + 8 + 1); // Set size to CmdHdr + padded data + MAC - apdu_out[APDU_HEADER_SIZE + padded_data_size + 8 + 1] = 0; // Le + 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, 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, 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"); + 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; + } + + // TODO: In case of COMM_FULL we would need to decrypt response here as well. + // And in case of COMM_MAC we would need to verify the MAC here, if we want to verify the card. + + 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, 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; } @@ -331,46 +404,29 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * .optional_sdm_settings = settings->optional_sdm_settings, }; - // ------- Assemble the actual command size_t settings_size = ntag424_calc_file_write_settings_size(settings); - uint8_t lc = 1 + settings_size; // CmdHeader + size */ - uint8_t cmd_header[] = { - 0x90, 0x5f, 0x00, 0x00, - lc, - fileno + 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 }; - uint8_t cmd[256] = {0}; - memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], (void *)&write_settings, settings_size); - cmd[sizeof(cmd_header) + settings_size] = 0x00; - uint8_t apdu_out[256] = {0}; - int apdu_out_size = 256; - ntag424_comm_full_encrypt_apdu(cmd, apdu_out, &apdu_out_size, session_keys); // ------- Actually send the APDU - const size_t RESPONSE_LENGTH = 8 + 2; - int outlen; - uint8_t resp[RESPONSE_LENGTH]; - int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to send apdu"); - return res; - } + int response_length = 8 + 2; + uint8_t response[response_length]; - if (outlen != RESPONSE_LENGTH) { - PrintAndLogEx(ERR, "Incorrect response length: %d, %02X%02X", outlen, resp[outlen - 2], resp[outlen - 1]); - return PM3_ESOFT; - } + int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Failed to get file settings"); - return PM3_ESOFT; - } session_keys->command_counter++; // Should this be incremented only on success? - return PM3_SUCCESS; + return res; } static void ntag424_print_file_settings(uint8_t fileno, const ntag424_file_settings_t *settings) { @@ -424,53 +480,50 @@ static int ntag424_select_application(void) { } static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) { - const size_t RESPONSE_LENGTH = 16 + 2; - uint8_t cmd[] = {0x90, 0x71, 0x00, 0x00, 0x02, keyno, 0x00, 0x00}; - uint8_t resp[RESPONSE_LENGTH]; - int outlen = 0; - int res; + uint8_t key_number[2] = { keyno, 0x00 }; - res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); + 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, response, &response_length, COMM_PLAIN, NULL, 0x91, 0xAF); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if (outlen != RESPONSE_LENGTH || resp[RESPONSE_LENGTH - 2] != 0x91 || resp[RESPONSE_LENGTH - 1] != 0xAF) { + 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, resp, out, 16); + aes_decode(iv, key, response, out, 16); return PM3_SUCCESS; } -static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response) { - uint8_t cmd_header[] = { 0x90, 0xAF, 0x00, 0x00, 0x20 }; - - uint8_t cmd[sizeof(cmd_header) + 32 + 1] = {0}; - - memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], challenge, 32); - - const size_t RESPONSE_LENGTH = 256; - uint8_t resp[RESPONSE_LENGTH]; - int outlen = 0; - int res; +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]; - res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); + int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); if (res != PM3_SUCCESS) { return res; } - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Challenge failed: wrong key?"); - return PM3_ESOFT; - } - - memcpy(response, resp, outlen - 2); + memcpy(response_out, response, response_length - 2); return PM3_SUCCESS; } @@ -571,54 +624,47 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s return PM3_SUCCESS; } -#define MAX_WRITE_APDU 248 +#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_file(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *in) { - const size_t RESPONSE_LENGTH = 2; +static int ntag424_write_data(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *in) { 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; } - // 248 + - uint8_t cmd_header[] = { 0x90, 0x8d, 0x00, 0x00, 0x07 + num_bytes, 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_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[512] = {0}; + uint8_t cmd[256] = {0}; memcpy(cmd, cmd_header, sizeof(cmd_header)); memcpy(&cmd[sizeof(cmd_header)], in, num_bytes); - size_t total_size = sizeof(cmd_header) + num_bytes + 1; //(Le) + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_WRITE_DATA, + .lc = sizeof(cmd_header) + num_bytes, + .data = cmd, + }; - uint8_t resp[RESPONSE_LENGTH]; - int outlen = 0; - int res; + int response_length = 2; + uint8_t response[response_length]; - res = ExchangeAPDU14a(cmd, total_size, false, true, resp, RESPONSE_LENGTH, &outlen); + int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to send apdu"); return res; } - if (outlen != RESPONSE_LENGTH) { - PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); - return PM3_ESOFT; - } - - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Failed to write file"); - return PM3_ESOFT; - } - if (remainder > 0) { - return ntag424_write_file(fileno, offset + num_bytes, remainder, &in[num_bytes]); + return ntag424_write_data(fileno, offset + num_bytes, remainder, &in[num_bytes]); } return PM3_SUCCESS; @@ -626,35 +672,29 @@ static int ntag424_write_file(uint8_t fileno, uint16_t offset, uint16_t num_byte // Read file from card. Only supports plain communications mode. Authentication must be done // first unless file has free read access. -static int ntag424_read_file(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *out) { - const size_t RESPONSE_LENGTH = num_bytes + 2; - - uint8_t cmd[] = { 0x90, 0xad, 0x00, 0x00, 0x07, fileno, - (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset - (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00, //size - 0x00 - }; - uint8_t resp[RESPONSE_LENGTH]; - int outlen = 0; - int res; +static int ntag424_read_data(uint8_t fileno, uint16_t offset, uint16_t num_bytes, uint8_t *out) { + uint8_t cmd[] = { + fileno, + (uint8_t)offset, (uint8_t)(offset << 8), (uint8_t)(offset << 16), // offset + (uint8_t)num_bytes, (uint8_t)(num_bytes >> 8), 0x00 + }; - res = ExchangeAPDU14a(cmd, sizeof(cmd), false, true, resp, RESPONSE_LENGTH, &outlen); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to send apdu"); - return res; - } + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_READ_DATA, + .lc = sizeof(cmd), + .data = cmd, + }; - if (outlen != RESPONSE_LENGTH) { - PrintAndLogEx(ERR, "Incorrect response length: %d, %s", outlen, sprint_hex(resp, 2)); - return PM3_ESOFT; - } + int response_length = num_bytes + 2; + uint8_t response[response_length]; - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Failed to read file"); - return PM3_ESOFT; + int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + if (res != PM3_SUCCESS) { + return res; } - memcpy(out, resp, num_bytes); + memcpy(out, response, num_bytes); return PM3_SUCCESS; } @@ -671,57 +711,34 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, memcpy(key, new_key, 16); } - // ------- Calculate KeyData - uint8_t keydata[32] = {0}; - memcpy(keydata, key, 16); - keydata[16] = version; + // ------- 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(&keydata[17], crc, 4); - keydata[21] = 0x80; - key_data_len = 16 + 4 + 1; + memcpy(&key_cmd_data[18], crc, sizeof(crc)); + key_data_len = sizeof(keyno) + sizeof(key) + sizeof(version) + sizeof(crc); } else { - keydata[17] = 0x80; - key_data_len = 16 + 1; + key_data_len = sizeof(keyno) + sizeof(key) + sizeof(version); } - // ------- Assemble APDU - uint8_t cmd_header[] = { - 0x90, 0xC4, 0x00, 0x00, key_data_len + 1, keyno + APDU_t apdu = { + .cla = 0x90, + .ins = NTAG424_CMD_CHANGE_KEY, + .lc = key_data_len, + .data = key_cmd_data }; - uint8_t cmd[512] = {0}; - memcpy(cmd, cmd_header, sizeof(cmd_header)); - memcpy(&cmd[sizeof(cmd_header)], keydata, key_data_len); - - uint8_t apdu_out[256]; - int apdu_out_size = 256; - ntag424_comm_full_encrypt_apdu(cmd, apdu_out, &apdu_out_size, session_keys); + int response_length = 8 + 2; + uint8_t response[response_length]; - - // ------- Actually send the APDU - const size_t RESPONSE_LENGTH = 8 + 2; - int outlen; - uint8_t resp[RESPONSE_LENGTH]; - int res = ExchangeAPDU14a(apdu_out, apdu_out_size, false, true, resp, RESPONSE_LENGTH, &outlen); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to send apdu"); - return res; - } - - if (outlen < 2) { - PrintAndLogEx(ERR, "Incorrect response length: %d", outlen); - return PM3_ESOFT; - } - - if (resp[outlen - 2] != 0x91 || resp[outlen - 1] != 0x00) { - PrintAndLogEx(ERR, "Error when changing key. Wrong old key?"); - return PM3_ESOFT; - } + int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); session_keys->command_counter++; // Should this be incremented only on success? - return PM3_SUCCESS; + return res; } @@ -871,7 +888,7 @@ static int CmdHF_ntag424_read(const char *Cmd) { uint8_t data[512]; - res = ntag424_read_file(fileno, offset, read_length, data); + res = ntag424_read_data(fileno, offset, read_length, data); if (res != PM3_SUCCESS) { DropField(); return res; @@ -947,7 +964,7 @@ static int CmdHF_ntag424_write(const char *Cmd) { } } - res = ntag424_write_file(fileno, offset, datalen, data); + res = ntag424_write_data(fileno, offset, datalen, data); if (res != PM3_SUCCESS) { DropField(); return res; @@ -990,7 +1007,7 @@ static int CmdHF_ntag424_getfilesettings(const char *Cmd) { } ntag424_file_settings_t settings; - res = ntag424_read_file_settings(fileno, &settings); + res = ntag424_get_file_settings(fileno, &settings); DropField(); if (res != PM3_SUCCESS) { return res; @@ -1151,7 +1168,7 @@ static int CmdHF_ntag424_changefilesettings(const char *Cmd) { } ntag424_file_settings_t settings; - res = ntag424_read_file_settings(fileno, &settings); + res = ntag424_get_file_settings(fileno, &settings); if (res != PM3_SUCCESS) { DropField(); return res; diff --git a/doc/commands.json b/doc/commands.json index d20870949d..9499df7e15 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6806,7 +6806,7 @@ }, "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 SDMMetaRead, SDMFileRead, Reserved and SDMCtrRet 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", + "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" ], @@ -12012,6 +12012,6 @@ "metadata": { "commands_extracted": 696, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2023-10-27T16:08:32" + "extracted_on": "2023-10-29T12:42:07" } } \ No newline at end of file From 912c1262664978a406bc54c92bb340e83767de38 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Sun, 29 Oct 2023 16:44:53 +0100 Subject: [PATCH 08/10] Added encrypted comms to ReadData and WriteData commands This allows reading and writing to the "proprietary" file (number 3), or file 2 if it's set to "full" communications mode. --- client/src/cmdhfntag424.c | 227 +++++++++++++++++++++++--------------- doc/commands.json | 12 +- 2 files changed, 144 insertions(+), 95 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index f01df9605e..cc308cc2af 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -45,45 +45,6 @@ #define NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_1 0x71 #define NTAG424_CMD_AUTHENTICATE_EV2_FIRST_PART_2 0xAF -static int CmdHelp(const char *Cmd); - -static int CmdHF_ntag424_view(const char *Cmd) { - - CLIParserContext *ctx; - CLIParserInit(&ctx, "hf ntag424 view", - "Print a NTAG 424 DNA dump file (bin/eml/json)", - "hf ntag424 view -f hf-ntag424-01020304-dump.bin" - ); - void *argtable[] = { - arg_param_begin, - arg_str1("f", "file", "", "Specify a filename for dump file"), - arg_lit0("v", "verbose", "Verbose output"), - 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); - bool verbose = arg_get_lit(ctx, 2); - CLIParserFree(ctx); - - // read dump file - uint8_t *dump = NULL; - size_t bytes_read = NTAG424_MAX_BYTES; - int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, NTAG424_MAX_BYTES); - if (res != PM3_SUCCESS) { - return res; - } - - if (verbose) { - PrintAndLogEx(INFO, "File: " _YELLOW_("%s"), filename); - PrintAndLogEx(INFO, "File size %zu bytes", bytes_read); - } - - free(dump); - return PM3_SUCCESS; -} - // // Original from https://github.com/rfidhacking/node-sdm/ // @@ -115,6 +76,13 @@ typedef enum { 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) @@ -231,7 +199,7 @@ static int ntag424_calc_file_write_settings_size(const ntag424_file_settings_t * return ntag424_calc_file_settings_size(settings) - 4; } -static void ntag424_calc_iv(ntag424_session_keys_t *session_keys, uint8_t *out_ivc) { +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), @@ -242,11 +210,21 @@ static void ntag424_calc_iv(ntag424_session_keys_t *session_keys, uint8_t *out_i 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 command_header, uint8_t *data, uint8_t datalen, uint8_t *out_mac) { +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], - command_header, + 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; @@ -264,7 +242,7 @@ static void ntag424_calc_mac(ntag424_session_keys_t *session_keys, uint8_t comma free(mac_input); } -static int ntag424_comm_mac_apdu(APDU_t *apdu, int apdu_max_data_size, ntag424_session_keys_t *session_keys) { +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; @@ -272,41 +250,45 @@ static int ntag424_comm_mac_apdu(APDU_t *apdu, int apdu_max_data_size, ntag424_s return PM3_EOVFLOW; } - ntag424_calc_mac(session_keys, apdu->ins, apdu->data[0], &apdu->data[1], size - 1, &apdu->data[size]); - + 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 apdu_max_data_size, ntag424_session_keys_t *session_keys) { +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_iv(session_keys, ivc); + ntag424_calc_send_iv(session_keys, ivc); int size = apdu->lc; - size_t encrypt_data_size = size - 1; + 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 (padded_data_size + 1 > apdu_max_data_size) { + 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[1], encrypt_data_size); // We encrypt everything except the CmdHdr (first byte in 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[1], padded_data_size); + aes_encode(ivc, session_keys->encryption, temp_buffer, &apdu->data[command_header_length], padded_data_size); - apdu->lc = (uint8_t)(1 + padded_data_size); // Set size to CmdHdr + padded data + 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, 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) { +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; @@ -324,14 +306,14 @@ static int ntag424_exchange_apdu(APDU_t *apdu, uint8_t *response, int *response_ } if (comm_mode == COMM_FULL) { - res = ntag424_comm_encrypt_apdu(apdu, buffer_length, session_keys); + 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, buffer_length, session_keys); + res = ntag424_comm_mac_apdu(apdu, command_header_length, buffer_length, session_keys); if (res != PM3_SUCCESS) { return res; } @@ -346,7 +328,7 @@ static int ntag424_exchange_apdu(APDU_t *apdu, uint8_t *response, int *response_ res = ExchangeAPDU14a(cmd, apdu_length + 1, false, true, response, *response_length, response_length); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Failed to exchange APDU"); + PrintAndLogEx(ERR, "Failed to exchange APDU: %d", res); return res; } @@ -363,8 +345,18 @@ static int ntag424_exchange_apdu(APDU_t *apdu, uint8_t *response, int *response_ return PM3_ESOFT; } - // TODO: In case of COMM_FULL we would need to decrypt response here as well. - // And in case of COMM_MAC we would need to verify the MAC here, if we want to verify the card. + // 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; } @@ -382,7 +374,7 @@ static int ntag424_get_file_settings(uint8_t fileno, ntag424_file_settings_t *se .extended_apdu = false }; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); if (res != PM3_SUCCESS) { return res; } @@ -422,10 +414,7 @@ static int ntag424_write_file_settings(uint8_t fileno, ntag424_file_settings_t * int response_length = 8 + 2; uint8_t response[response_length]; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); - - - session_keys->command_counter++; // Should this be incremented only on success? + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); return res; } @@ -492,7 +481,7 @@ static int ntag424_auth_first_step(uint8_t keyno, uint8_t *key, uint8_t *out) { int response_length = 16 + 2; uint8_t response[response_length]; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0xAF); + int res = ntag424_exchange_apdu(&apdu, 2, response, &response_length, COMM_PLAIN, NULL, 0x91, 0xAF); if (res != PM3_SUCCESS) { return res; } @@ -518,7 +507,7 @@ static int ntag424_auth_second_step(uint8_t *challenge, uint8_t *response_out) { int response_length = 256; uint8_t response[response_length]; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + int res = ntag424_exchange_apdu(&apdu, 0x20, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); if (res != PM3_SUCCESS) { return res; } @@ -628,7 +617,7 @@ static int ntag424_authenticate_ev2_first(uint8_t keyno, uint8_t *key, ntag424_s // 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) { +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 @@ -655,16 +644,16 @@ static int ntag424_write_data(uint8_t fileno, uint16_t offset, uint16_t num_byte .data = cmd, }; - int response_length = 2; + int response_length = 8 + 2; // potential MAC and result uint8_t response[response_length]; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + 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]); + return ntag424_write_data(fileno, offset + num_bytes, remainder, &in[num_bytes], comm_mode, session_keys); } return PM3_SUCCESS; @@ -672,8 +661,8 @@ static int ntag424_write_data(uint8_t fileno, uint16_t offset, uint16_t num_byte // 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) { - uint8_t cmd[] = { +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 @@ -682,14 +671,14 @@ static int ntag424_read_data(uint8_t fileno, uint16_t offset, uint16_t num_bytes APDU_t apdu = { .cla = 0x90, .ins = NTAG424_CMD_READ_DATA, - .lc = sizeof(cmd), - .data = cmd, + .lc = sizeof(cmd_header), + .data = cmd_header, }; - int response_length = num_bytes + 2; + 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, response, &response_length, COMM_PLAIN, NULL, 0x91, 0x00); + int res = ntag424_exchange_apdu(&apdu, sizeof(cmd_header), response, &response_length, comm_mode, session_keys, 0x91, 0x00); if (res != PM3_SUCCESS) { return res; } @@ -734,12 +723,47 @@ static int ntag424_change_key(uint8_t keyno, uint8_t *new_key, uint8_t *old_key, int response_length = 8 + 2; uint8_t response[response_length]; - int res = ntag424_exchange_apdu(&apdu, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); + int res = ntag424_exchange_apdu(&apdu, 1, response, &response_length, COMM_FULL, session_keys, 0x91, 0x00); + return res; +} - session_keys->command_counter++; // Should this be incremented only on success? +static int CmdHelp(const char *Cmd); - return res; +static int CmdHF_ntag424_view(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf ntag424 view", + "Print a NTAG 424 DNA dump file (bin/eml/json)", + "hf ntag424 view -f hf-ntag424-01020304-dump.bin" + ); + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "Specify a filename for dump file"), + arg_lit0("v", "verbose", "Verbose output"), + 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); + bool verbose = arg_get_lit(ctx, 2); + CLIParserFree(ctx); + + // read dump file + uint8_t *dump = NULL; + size_t bytes_read = NTAG424_MAX_BYTES; + int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, NTAG424_MAX_BYTES); + if (res != PM3_SUCCESS) { + return res; + } + + if (verbose) { + PrintAndLogEx(INFO, "File: " _YELLOW_("%s"), filename); + PrintAndLogEx(INFO, "File size %zu bytes", bytes_read); + } + + free(dump); + return PM3_SUCCESS; } static int CmdHF_ntag424_info(const char *Cmd) { @@ -769,7 +793,6 @@ static int ntag424_cli_get_auth_information(CLIParserContext *ctx, int key_no_in CLIGetHexWithReturn(ctx, key_index, key, &keylen); if (keylen != 16) { - PrintAndLogEx(ERR, "Key must be 16 bytes"); return PM3_ESOFT; } @@ -840,6 +863,7 @@ static int CmdHF_ntag424_read(const char *Cmd) { 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); @@ -851,10 +875,20 @@ static int CmdHF_ntag424_read(const char *Cmd) { int fileno = arg_get_int(ctx, 1); if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { - PrintAndLogEx(INFO, "Reading unauthenticated"); auth = 0; - } else { - PrintAndLogEx(INFO, "Reading authenticated"); + } + + 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); @@ -875,8 +909,9 @@ static int CmdHF_ntag424_read(const char *Cmd) { return res; } + ntag424_session_keys_t session_keys; if (auth) { - res = ntag424_authenticate_ev2_first(keyno, key, NULL); + res = ntag424_authenticate_ev2_first(keyno, key, &session_keys); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); DropField(); @@ -888,7 +923,7 @@ static int CmdHF_ntag424_read(const char *Cmd) { uint8_t data[512]; - res = ntag424_read_data(fileno, offset, read_length, data); + res = ntag424_read_data(fileno, offset, read_length, data, comm_mode, &session_keys); if (res != PM3_SUCCESS) { DropField(); return res; @@ -915,6 +950,7 @@ static int CmdHF_ntag424_write(const char *Cmd) { 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); @@ -926,10 +962,20 @@ static int CmdHF_ntag424_write(const char *Cmd) { int fileno = arg_get_int(ctx, 1); if (ntag424_cli_get_auth_information(ctx, 2, 3, &keyno, key) != PM3_SUCCESS) { - PrintAndLogEx(INFO, "Will write unauthenticated"); auth = 0; - } else { - PrintAndLogEx(INFO, "Will write authenticated"); + } + + 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); @@ -953,8 +999,9 @@ static int CmdHF_ntag424_write(const char *Cmd) { return res; } + ntag424_session_keys_t session_keys; if (auth) { - res = ntag424_authenticate_ev2_first(keyno, key, NULL); + res = ntag424_authenticate_ev2_first(keyno, key, &session_keys); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Failed to authenticate with key %d", keyno); DropField(); @@ -964,7 +1011,7 @@ static int CmdHF_ntag424_write(const char *Cmd) { } } - res = ntag424_write_data(fileno, offset, datalen, data); + res = ntag424_write_data(fileno, offset, datalen, data, comm_mode, &session_keys); if (res != PM3_SUCCESS) { DropField(); return res; diff --git a/doc/commands.json b/doc/commands.json index 9499df7e15..12bc7a5e14 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6887,9 +6887,10 @@ "-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" + "-l, --length Number of bytes to read", + "-m, --cmode Communicaton mode" ], - "usage": "hf ntag424 read [-h] -f [-n ] [-k ] [-o ] -l " + "usage": "hf ntag424 read [-h] -f [-n ] [-k ] [-o ] -l [-m ]" }, "hf ntag424 view": { "command": "hf ntag424 view", @@ -6918,9 +6919,10 @@ "-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" + "-d, --data Data to write", + "-m, --cmode Communicaton mode" ], - "usage": "hf ntag424 write [-h] -f [-n ] [-k ] [-o ] -d " + "usage": "hf ntag424 write [-h] -f [-n ] [-k ] [-o ] -d [-m ]" }, "hf plot": { "command": "hf plot", @@ -12012,6 +12014,6 @@ "metadata": { "commands_extracted": 696, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2023-10-29T12:42:07" + "extracted_on": "2023-10-29T15:43:05" } } \ No newline at end of file From efb645636bc317c4e0f92fd5517f8cc157284644 Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Sun, 29 Oct 2023 16:56:04 +0100 Subject: [PATCH 09/10] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b794f4db2..1a8720a223 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) - 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) - Fixed some wrong synchronization waits in usb_write() to increase the communication speed (@wh201906) From d4f8c42d3c9969bb7eb0569dde724630f7d6c85f Mon Sep 17 00:00:00 2001 From: Daniel Karling Date: Sun, 29 Oct 2023 17:13:27 +0100 Subject: [PATCH 10/10] Added additional command examples --- client/src/cmdhfntag424.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhfntag424.c b/client/src/cmdhfntag424.c index cc308cc2af..cbf34de6e8 100644 --- a/client/src/cmdhfntag424.c +++ b/client/src/cmdhfntag424.c @@ -854,11 +854,13 @@ 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 2 -n 0 -k 00000000000000000000000000000000 -o 0 -l 256"); + "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), (default 2)"), + 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)"), @@ -941,7 +943,8 @@ static int CmdHF_ntag424_write(const char *Cmd) { CLIParserContext *ctx; 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"); + "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,