mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-06 01:17:56 +00:00
9126 lines
347 KiB
C
9126 lines
347 KiB
C
//-----------------------------------------------------------------------------
|
|
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// See LICENSE.txt for the text of the license.
|
|
//-----------------------------------------------------------------------------
|
|
// High frequency MIFARE ULTRALIGHT (C) commands
|
|
//-----------------------------------------------------------------------------
|
|
#include "cmdhfmfu.h"
|
|
#include <ctype.h>
|
|
#include "cmdparser.h"
|
|
#include "commonutil.h"
|
|
#include "crypto/libpcrypto.h"
|
|
#include "des.h"
|
|
#include "aes.h"
|
|
#include "cmdhfmf.h"
|
|
#include "cmdhf14a.h"
|
|
#include "mifare/mifarehost.h" // mf_eml_set_mem_xt
|
|
#include "comms.h"
|
|
#include "protocols.h"
|
|
#include "generator.h"
|
|
#include "nfc/ndef.h"
|
|
#include "cliparser.h"
|
|
#include "cmdmain.h"
|
|
#include "amiibo.h" // amiiboo fcts
|
|
#include "base64.h"
|
|
#include "util_posix.h" // msclock
|
|
#include "fileutils.h" // saveFile
|
|
#include "cmdtrace.h" // trace list
|
|
#include "preferences.h" // setDeviceDebugLevel
|
|
#include "crc16.h"
|
|
#include "crypto/originality.h"
|
|
#include "util.h"
|
|
#include <pthread.h>
|
|
#include <vec/vec.h>
|
|
|
|
#define MAX_UL_BLOCKS 0x0F
|
|
#define MAX_ULC_BLOCKS 0x2F
|
|
#define MAX_ULEV1a_BLOCKS 0x13
|
|
#define MAX_ULEV1b_BLOCKS 0x28
|
|
#define MAX_NTAG_203 0x29
|
|
#define MAX_NTAG_210 0x13
|
|
#define MAX_NTAG_212 0x28
|
|
#define MAX_NTAG_213 0x2C
|
|
#define MAX_NTAG_215 0x86
|
|
#define MAX_NTAG_216 0xE6
|
|
#define MAX_NTAG_223_DNA 0x3B
|
|
#define MAX_NTAG_224_DNA 0x4B
|
|
#define MAX_NTAG_I2C_1K 0xE9
|
|
#define MAX_NTAG_I2C_2K 0xE9
|
|
#define MAX_MY_D_NFC 0xFF
|
|
#define MAX_MY_D_MOVE 0x25
|
|
#define MAX_MY_D_MOVE_LEAN 0x0F
|
|
#define MAX_UL_NANO_40 0x0A
|
|
#define MAX_UL_AES 0x37
|
|
#define MAX_ST25TN512 0x3F
|
|
#define MAX_ST25TN01K 0x3F
|
|
|
|
#define MIFAREU3P_KEY_SIZE 16
|
|
#define MIFAREULC_KEY_INDEX 3
|
|
|
|
// The Capability Container sits in block 3, the NDEF data area starts at block 4
|
|
// and READ takes a one byte block number, so block 255 is the last one reachable.
|
|
#define MFU_NDEF_CC_BLOCK 3
|
|
#define MFU_NDEF_FIRST_BLOCK 4
|
|
#define MFU_NDEF_MAX_BYTES ((0xFF - MFU_NDEF_FIRST_BLOCK + 1) * MFU_BLOCK_SIZE)
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
|
|
static const char *key_type[] = { "DataProtKey", "UIDRetrKey", "OriginalityKey" };
|
|
|
|
static uint8_t default_aes_keys[][16] = {
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // all zeroes
|
|
{ 0x42, 0x52, 0x45, 0x41, 0x4b, 0x4d, 0x45, 0x49, 0x46, 0x59, 0x4f, 0x55, 0x43, 0x41, 0x4e, 0x21 }, // 3des std key
|
|
{ 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46 }, // NFC-key
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }, // 0x00-0x0F
|
|
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // all ones
|
|
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // all FF
|
|
{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, // 11 22 33
|
|
{ 0x47, 0x45, 0x4D, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x4F, 0x53, 0x41, 0x4D, 0x50, 0x4C, 0x45 }, // gemalto
|
|
{ 0x56, 0x4c, 0x67, 0x56, 0x99, 0x69, 0x64, 0x9f, 0x17, 0xC6, 0xC6, 0x16, 0x01, 0x10, 0x4D, 0xCA } // Virtual dormakaba
|
|
};
|
|
|
|
static uint8_t default_3des_keys[][16] = {
|
|
{ 0x42, 0x52, 0x45, 0x41, 0x4b, 0x4d, 0x45, 0x49, 0x46, 0x59, 0x4f, 0x55, 0x43, 0x41, 0x4e, 0x21 }, // 3des std key
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // all zeroes
|
|
{ 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46 }, // NFC-key
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }, // 0x00-0x0F
|
|
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // all ones
|
|
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // all FF
|
|
{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, // 11 22 33
|
|
{ 0x47, 0x45, 0x4D, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x4F, 0x53, 0x41, 0x4D, 0x50, 0x4C, 0x45 } // gemalto
|
|
};
|
|
|
|
static uint8_t default_pwd_pack[][4] = {
|
|
{0xFF, 0xFF, 0xFF, 0xFF}, // PACK 0x00,0x00 -- factory default
|
|
{0x4E, 0x45, 0x78, 0x54}, // NExT
|
|
{0xB6, 0xAA, 0x55, 0x8D}, // copykey
|
|
};
|
|
|
|
static uint64_t UL_TYPES_ARRAY[] = {
|
|
MFU_TT_UNKNOWN, MFU_TT_UL,
|
|
MFU_TT_UL_C, MFU_TT_UL_EV1_48,
|
|
MFU_TT_UL_EV1_128, MFU_TT_NTAG,
|
|
MFU_TT_NTAG_203, MFU_TT_NTAG_210,
|
|
MFU_TT_NTAG_212, MFU_TT_NTAG_213,
|
|
MFU_TT_NTAG_215, MFU_TT_NTAG_216,
|
|
MFU_TT_NTAG_223_DNA, MFU_TT_NTAG_223_DNA_SD,
|
|
MFU_TT_NTAG_224_DNA, MFU_TT_NTAG_224_DNA_SD,
|
|
MFU_TT_MY_D, MFU_TT_MY_D_NFC,
|
|
MFU_TT_MY_D_MOVE, MFU_TT_MY_D_MOVE_NFC,
|
|
MFU_TT_MY_D_MOVE_LEAN, MFU_TT_NTAG_I2C_1K,
|
|
MFU_TT_NTAG_I2C_2K, MFU_TT_NTAG_I2C_1K_PLUS,
|
|
MFU_TT_NTAG_I2C_2K_PLUS, MFU_TT_FUDAN_UL,
|
|
MFU_TT_NTAG_213_F, MFU_TT_NTAG_216_F,
|
|
MFU_TT_UL_EV1, MFU_TT_UL_NANO_40,
|
|
MFU_TT_NTAG_213_TT, MFU_TT_NTAG_213_C,
|
|
MFU_TT_MAGIC_1A, MFU_TT_MAGIC_1B,
|
|
MFU_TT_MAGIC_NTAG, MFU_TT_NTAG_210u,
|
|
MFU_TT_UL_MAGIC, MFU_TT_UL_C_MAGIC,
|
|
MFU_TT_UL_AES,
|
|
MFU_TT_ST25TN512, MFU_TT_ST25TN01K,
|
|
|
|
};
|
|
|
|
static uint8_t UL_MEMORY_ARRAY[ARRAYLEN(UL_TYPES_ARRAY)] = {
|
|
// UNKNOWN, UL, UL_C, UL_EV1_48, UL_EV1_128,
|
|
MAX_UL_BLOCKS, MAX_UL_BLOCKS, MAX_ULC_BLOCKS, MAX_ULEV1a_BLOCKS, MAX_ULEV1b_BLOCKS,
|
|
// NTAG, NTAG_203, NTAG_210, NTAG_212,
|
|
MAX_NTAG_203, MAX_NTAG_203, MAX_NTAG_210, MAX_NTAG_212,
|
|
// NTAG_213, NTAG_215, NTAG_216,
|
|
MAX_NTAG_213, MAX_NTAG_215, MAX_NTAG_216,
|
|
// NTAG_223_DNA, NTAG_223_DNA_SD, NTAG_224_DNA, NTAG_224_DNA_SD,
|
|
MAX_NTAG_223_DNA, MAX_NTAG_223_DNA, MAX_NTAG_224_DNA, MAX_NTAG_224_DNA,
|
|
// MY_D, MY_D_NFC, MY_D_MOVE, MY_D_MOVE_NFC, MY_D_MOVE_LEAN,
|
|
MAX_UL_BLOCKS, MAX_MY_D_NFC, MAX_MY_D_MOVE, MAX_MY_D_MOVE, MAX_MY_D_MOVE_LEAN,
|
|
// NTAG_I2C_1K, NTAG_I2C_2K, NTAG_I2C_1K_PLUS, NTAG_I2C_2K_PLUS,
|
|
MAX_NTAG_I2C_1K, MAX_NTAG_I2C_2K, MAX_NTAG_I2C_1K, MAX_NTAG_I2C_2K,
|
|
// FUDAN_UL, NTAG_213_F, NTAG_216_F, UL_EV1, UL_NANO_40,
|
|
MAX_UL_BLOCKS, MAX_NTAG_213, MAX_NTAG_216, MAX_ULEV1a_BLOCKS, MAX_UL_NANO_40,
|
|
// NTAG_213_TT, NTAG_213_C,
|
|
MAX_NTAG_213, MAX_NTAG_213,
|
|
// MAGIC_1A, MAGIC_1B, MAGIC_NTAG,
|
|
MAX_UL_BLOCKS, MAX_UL_BLOCKS, MAX_NTAG_216,
|
|
// NTAG_210u, UL_MAGIC, UL_C_MAGIC
|
|
MAX_NTAG_210, MAX_UL_BLOCKS, MAX_ULC_BLOCKS, MAX_UL_AES,
|
|
// ST25TN512, ST25TN01K,
|
|
MAX_ST25TN512, MAX_ST25TN01K,
|
|
};
|
|
|
|
static const ul_family_t ul_family[] = {
|
|
{"UL-C", "UL-C", "\x00\x00\x00\x00\x00\x00\x00\x00"},
|
|
{"UL", "MF0UL1001DUx", "\x00\x04\x03\x01\x00\x00\x0B\x03"},
|
|
{"UL EV1 48", "MF0UL1101DUx", "\x00\x04\x03\x01\x01\x00\x0B\x03"},
|
|
{"UL EV1 48", "MF0ULH1101DUx", "\x00\x04\x03\x02\x01\x00\x0B\x03"},
|
|
{"UL EV1 48", "MF0UL1141DUF", "\x00\x04\x03\x03\x01\x00\x0B\x03"},
|
|
{"UL EV1 128", "MF0UL2101Dxy", "\x00\x04\x03\x01\x01\x00\x0E\x03"},
|
|
{"UL EV1 128", "MF0UL2101DUx", "\x00\x04\x03\x02\x01\x00\x0E\x03"},
|
|
{"UL Ev1 n/a ", "MF0UL3101DUx", "\x00\x04\x03\x01\x01\x00\x11\x03"},
|
|
{"UL Ev1 n/a", "MF0ULH3101DUx", "\x00\x04\x03\x02\x01\x00\x11\x03"},
|
|
{"UL Ev1 n/a", "MF0UL5101DUx", "\x00\x04\x03\x01\x01\x00\x13\x03"},
|
|
{"NTAG 210", "NT2L1011F0DUx", "\x00\x04\x04\x01\x01\x00\x0B\x03"},
|
|
{"NTAG 210", "NT2H1011G0DUD", "\x00\x04\x04\x02\x01\x00\x0B\x03"},
|
|
{"NTAG 212", "NT2L1211F0DUx", "\x00\x04\x04\x01\x01\x00\x0E\x03"},
|
|
{"NTAG 213", "NT2H1311G0DUx", "\x00\x04\x04\x02\x01\x00\x0F\x03"},
|
|
{"NTAG", "NT2H1411G0DUx", "\x00\x04\x04\x02\x01\x01\x11\x03"},
|
|
{"NTAG 215", "NT2H1511G0DUx", "\x00\x04\x04\x02\x01\x00\x11\x03"},
|
|
{"NTAG 215", "NT2H1511F0Dxy", "\x00\x04\x04\x04\x01\x00\x11\x03"},
|
|
{"NTAG 216", "NT2H1611G0DUx", "\x00\x04\x04\x02\x01\x00\x13\x03"},
|
|
{"NTAG 213F", "NT2H1311F0Dxy", "\x00\x04\x04\x04\x01\x00\x0F\x03"},
|
|
{"NTAG 216F", "NT2H1611F0Dxy", "\x00\x04\x04\x04\x01\x00\x13\x03"},
|
|
{"NTAG 213C", "NT2H1311C1DTL", "\x00\x04\x04\x02\x01\x01\x0F\x03"},
|
|
{"NTAG 213TT", "NT2H1311TTDUx", "\x00\x04\x04\x02\x03\x00\x0F\x03"},
|
|
{"NTAG 223 DNA", "NT2H2331G0", "\x00\x04\x04\x02\x04\x00\x0F\x03"},
|
|
{"NTAG 223 DNA SD", "NT2H2331S0", "\x00\x04\x04\x08\x04\x00\x0F\x03"},
|
|
{"NTAG 224 DNA", "NT2H2421G0", "\x00\x04\x04\x02\x05\x00\x10\x03"},
|
|
{"NTAG 224 DNA SD", "NT2H2421S0", "\x00\x04\x04\x08\x05\x00\x10\x03"},
|
|
{"NTAG I2C 1k", "NT3H1101W0FHK", "\x00\x04\x04\x05\x02\x00\x13\x03"},
|
|
{"NTAG I2C 1k", "NT3H1101W0FHK_Variant", "\x00\x04\x04\x05\x02\x01\x13\x03"},
|
|
{"NTAG I2C 2k", "NT3H1201W0FHK", "\x00\x04\x04\x05\x02\x00\x15\x03"},
|
|
{"NTAG I2C 2k", "NT3H1201", "\x00\x04\x04\x05\x02\x01\x15\x03"},
|
|
{"NTAG I2C 1k Plus", "NT3H2111", "\x00\x04\x04\x05\x02\x02\x13\x03"},
|
|
{"NTAG I2C 2k Plus", "NT3H2211", "\x00\x04\x04\x05\x02\x02\x15\x03"},
|
|
{"NTAG unk", "nhs", "\x00\x04\x04\x06\x00\x00\x13\x03"},
|
|
{"UL NANO 40", "MF0UN0001DUx 17pF", "\x00\x04\x03\x01\x02\x00\x0B\x03"},
|
|
{"UL NANO", "MF0UN1001DUx 17pF", "\x00\x04\x03\x01\x03\x00\x0B\x03"},
|
|
{"UL NANO 40", "MF0UNH0001DUx 50pF", "\x00\x04\x03\x02\x02\x00\x0B\x03"},
|
|
{"UL NANO", "MF0UNH1001DUx 50pF", "\x00\x04\x03\x02\x03\x00\x0B\x03"},
|
|
{"NTAG 210u", "NT2L1001G0DUx", "\x00\x04\x04\x01\x02\x00\x0B\x03"},
|
|
{"NTAG 210u", "NT2H1001G0DUx", "\x00\x04\x04\x02\x02\x00\x0B\x03"},
|
|
{"UL EV1 128", "Mikron JSC Russia EV1", "\x00\x34\x21\x01\x01\x00\x0E\x03"},
|
|
{"NTAG 213", "Shanghai Feiju NTAG", "\x00\x53\x04\x02\x01\x00\x0F\x03"},
|
|
{"NTAG 215", "Shanghai Feiju NTAG", "\x00\x05\x34\x02\x01\x00\x11\x03"},
|
|
{"UL AES", "MF0AES2001DUD", "\x00\x04\x03\x01\x04\x00\x0F\x03"},
|
|
};
|
|
|
|
static bool compare_ul_family(const uint8_t *d, uint8_t n) {
|
|
if (d == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (n > 8) {
|
|
n = 8;
|
|
}
|
|
|
|
for (int i = 0; i < ARRAYLEN(ul_family); ++i) {
|
|
if (memcmp(d, ul_family[i].version, n) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------
|
|
// get version nxp product type
|
|
static const char *getProductTypeStr(uint8_t id) {
|
|
static char buf[20];
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
switch (id) {
|
|
case 3:
|
|
return "Ultralight";
|
|
case 4:
|
|
return "NTAG";
|
|
default:
|
|
snprintf(buf, sizeof(buf), "%02X, unknown", id);
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
static int ul_print_nxp_silicon_info(const uint8_t *card_uid) {
|
|
|
|
if (card_uid[0] != 0x04) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
uint8_t uid[7];
|
|
memcpy(&uid, card_uid, 7);
|
|
|
|
uint16_t waferCoordX = ((uid[6] & 3) << 8) | uid[1];
|
|
uint16_t waferCoordY = ((uid[6] & 12) << 6) | uid[2];
|
|
uint32_t waferCounter = (
|
|
(uid[4] << 5) |
|
|
((uid[6] & 0xF0) << 17) |
|
|
(uid[5] << 13) |
|
|
(uid[3] >> 3)
|
|
);
|
|
uint8_t testSite = uid[3] & 7;
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Silicon Information"));
|
|
PrintAndLogEx(INFO, " Wafer Counter: %" PRId32 " ( 0x%02" PRIX32 " )", waferCounter, waferCounter);
|
|
PrintAndLogEx(INFO, " Wafer Coordinates: x %" PRId16 ", y %" PRId16 " (0x%02" PRIX16 ", 0x%02" PRIX16 ")"
|
|
, waferCoordX
|
|
, waferCoordY
|
|
, waferCoordX
|
|
, waferCoordY
|
|
);
|
|
PrintAndLogEx(INFO, " Test Site: %u", testSite);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int get_ulc_3des_key_magic(uint64_t magic_type, uint8_t *key) {
|
|
|
|
mf_readblock_ex_t payload = {
|
|
.read_cmd = ISO14443A_CMD_READBLOCK,
|
|
.block_no = 0x2C,
|
|
};
|
|
|
|
if ((magic_type & MFU_TT_MAGIC_1A) == MFU_TT_MAGIC_1A) {
|
|
payload.wakeup = MF_WAKE_GEN1A;
|
|
payload.auth_cmd = 0;
|
|
} else if ((magic_type & MFU_TT_MAGIC_1B) == MFU_TT_MAGIC_1B) {
|
|
payload.wakeup = MF_WAKE_GEN1B;
|
|
payload.auth_cmd = 0;
|
|
} else if ((magic_type & MFU_TT_MAGIC_4) == MFU_TT_MAGIC_4) {
|
|
payload.wakeup = MF_WAKE_GDM_ALT;
|
|
payload.auth_cmd = 0;
|
|
} else if ((magic_type & MFU_TT_MAGIC_NTAG21X) == MFU_TT_MAGIC_NTAG21X) {
|
|
payload.wakeup = MF_WAKE_WUPA;
|
|
payload.auth_cmd = 0;
|
|
} else {
|
|
payload.wakeup = MF_WAKE_WUPA;
|
|
payload.auth_cmd = MIFARE_MAGIC_GDM_AUTH_KEY;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFARE_READBL_EX, (uint8_t *)&payload, sizeof(payload));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL_EX, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status == PM3_SUCCESS && resp.length == MFBLOCK_SIZE) {
|
|
uint8_t *d = resp.data.asBytes;
|
|
reverse_array(d, 8);
|
|
reverse_array(d + 8, 8);
|
|
memcpy(key, d, MFBLOCK_SIZE);
|
|
}
|
|
|
|
return resp.status;
|
|
}
|
|
|
|
/*
|
|
The 7 MSBits (=n) code the storage size itself based on 2^n,
|
|
the LSBit is set to '0' if the size is exactly 2^n
|
|
and set to '1' if the storage size is between 2^n and 2^(n+1).
|
|
*/
|
|
static const char *getUlev1CardSizeStr(uint8_t fsize) {
|
|
|
|
static char buf[40];
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
uint16_t usize = 1 << ((fsize >> 1) + 1);
|
|
uint16_t lsize = 1 << (fsize >> 1);
|
|
|
|
// is LSB set?
|
|
if (fsize & 1)
|
|
snprintf(buf, sizeof(buf), "%02X, (%u - %u bytes)", fsize, usize, lsize);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%02X, (%u bytes)", fsize, lsize);
|
|
return buf;
|
|
}
|
|
|
|
int ul_read_uid(uint8_t *uid) {
|
|
if (uid == NULL) {
|
|
PrintAndLogEx(WARNING, "UID is NULL");
|
|
return PM3_ESOFT;
|
|
}
|
|
// read uid from tag
|
|
clearCommandBuffer();
|
|
SendIso14aReader(ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_RATS, NULL, 0);
|
|
PacketResponseNG resp;
|
|
uint8_t sel_343 = 0;
|
|
if (WaitForIso14aReply(&resp, 2500, NULL, &sel_343) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
iso14a_card_select_t card;
|
|
memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
uint64_t select_status = sel_343;
|
|
// 0: couldn't read
|
|
// 1: OK with ATS
|
|
// 2: OK, no ATS
|
|
// 3: proprietary Anticollision
|
|
if (select_status == 0) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(uid, card.uid, 7);
|
|
|
|
if (card.uidlen != 7) {
|
|
PrintAndLogEx(WARNING, "Wrong sized UID, expected 7 bytes, got " _RED_("%d"), card.uidlen);
|
|
return PM3_ELENGTH;
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void ul_switch_on_field(void) {
|
|
clearCommandBuffer();
|
|
SendIso14aReader(ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT | ISO14A_NO_RATS, NULL, 0);
|
|
}
|
|
|
|
static int ul_send_cmd_raw(const uint8_t *cmd, uint8_t cmdlen, uint8_t *response, uint16_t responseLength, bool schann) {
|
|
clearCommandBuffer();
|
|
|
|
uint32_t param = (ISO14A_RAW | ISO14A_NO_DISCONNECT | ISO14A_APPEND_CRC | ISO14A_NO_RATS);
|
|
if (schann) {
|
|
param |= ISO14A_APPEND_CMAC;
|
|
}
|
|
SendIso14aReader(param, cmd, cmdlen);
|
|
PacketResponseNG resp;
|
|
uint16_t rlen_383 = 0;
|
|
if (WaitForIso14aReply(&resp, 1500, &rlen_383, NULL) == false) {
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if ((rlen_383 == 0) && responseLength) {
|
|
return PM3_EWRONGANSWER;
|
|
}
|
|
|
|
uint16_t resplen = (rlen_383 < responseLength) ? rlen_383 : responseLength;
|
|
memcpy(response, resp.data.asBytes, resplen);
|
|
return resplen;
|
|
}
|
|
|
|
static bool ul_select(iso14a_card_select_t *card) {
|
|
|
|
ul_switch_on_field();
|
|
|
|
PacketResponseNG resp;
|
|
if (WaitForIso14aReply(&resp, 2000, NULL, NULL) == false) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select timeout");
|
|
DropField();
|
|
return false;
|
|
} else {
|
|
|
|
uint16_t len = ((const iso14a_card_select_t *)resp.data.asBytes)->uidlen;
|
|
if (len == 0) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select failed");
|
|
DropField();
|
|
return false;
|
|
}
|
|
|
|
if (card) {
|
|
memcpy(card, resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ul_select_rats(iso14a_card_select_t *card) {
|
|
|
|
ul_switch_on_field();
|
|
|
|
PacketResponseNG resp;
|
|
uint8_t select_status = 0;
|
|
uint16_t ats_len = 0;
|
|
if (WaitForIso14aReply(&resp, 1500, NULL, &select_status) == false) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select timeout");
|
|
DropField();
|
|
return false;
|
|
} else {
|
|
|
|
uint16_t len = ((const iso14a_card_select_t *)resp.data.asBytes)->uidlen;
|
|
if (len == 0) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select failed");
|
|
DropField();
|
|
return false;
|
|
}
|
|
|
|
if (card) {
|
|
memcpy(card, resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
}
|
|
|
|
if (select_status == 2) { // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
|
|
// get ATS
|
|
uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, rats, sizeof(rats));
|
|
if (WaitForIso14aReply(&resp, 1500, &ats_len, NULL) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (card) {
|
|
card->ats_len = ats_len;
|
|
memcpy(card->ats, resp.data.asBytes, card->ats_len);
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This read command will at least return 16bytes.
|
|
static int ul_read(uint8_t page, uint8_t *response, uint16_t responseLength, bool schann) {
|
|
|
|
uint8_t cmd[] = {ISO14443A_CMD_READBLOCK, page};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, schann);
|
|
}
|
|
|
|
static int ul_comp_write(uint8_t page, const uint8_t *data, uint8_t datalen, bool schann) {
|
|
|
|
if (data == NULL) {
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t cmd[18];
|
|
memset(cmd, 0x00, sizeof(cmd));
|
|
datalen = (datalen > 16) ? 16 : datalen;
|
|
|
|
cmd[0] = ISO14443A_CMD_WRITEBLOCK;
|
|
cmd[1] = page;
|
|
memcpy(cmd + 2, data, datalen);
|
|
|
|
uint8_t response[1] = {0xFF};
|
|
ul_send_cmd_raw(cmd, 2 + datalen, response, sizeof(response), schann);
|
|
// ACK
|
|
if (response[0] == CARD_ACK) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
// NACK
|
|
return PM3_EWRONGANSWER;
|
|
}
|
|
|
|
static int ulc_requestAuthentication(uint8_t *nonce, uint16_t nonceLength) {
|
|
|
|
uint8_t cmd[] = {MIFARE_ULC_AUTH_1, 0x00};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), nonce, nonceLength, false);
|
|
}
|
|
|
|
int mfuc_test_authentication_support(void) {
|
|
SendIso14aReader(ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT, NULL, 0);
|
|
PacketResponseNG resp;
|
|
if (WaitForIso14aReply(&resp, 2500, NULL, NULL) == false) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select timeout");
|
|
DropField();
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
uint8_t nonce1[11] = {0x00};
|
|
int resplen = ulc_requestAuthentication(nonce1, sizeof(nonce1));
|
|
DropField();
|
|
if (resplen == 11) { // ULC nonce
|
|
return PM3_SUCCESS;
|
|
}
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
static int ulev1_requestAuthentication(const uint8_t *pwd, uint8_t *pack, uint16_t packLength) {
|
|
|
|
uint8_t cmd[] = {MIFARE_ULEV1_AUTH, pwd[0], pwd[1], pwd[2], pwd[3]};
|
|
int len = ul_send_cmd_raw(cmd, sizeof(cmd), pack, packLength, false);
|
|
// NACK tables different tags, but between 0-9 is a NEGATIVE response.
|
|
// ACK == 0xA
|
|
// should only give you PACK (4 byytes)
|
|
if (len == 1) {
|
|
return PM3_EWRONGANSWER;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
Default AES key is 00-00h. Both the data and UID one.
|
|
Data key is 00, UID is 01. Authenticity is 02h
|
|
Auth is 1A[Key ID][CRC] - AF[RndB] - AF[RndA][RndB'] - 00[RndA']
|
|
*/
|
|
static int ul3pass_authentication(const uint8_t *key, uint8_t keyno, bool switch_off_field, int retries, uint32_t *auths, uint32_t *ms, bool schann, bool try_auth, bool check_answer, bool use_fastread0, bool get_nonces, uint8_t *nonces, bool reset_field, uint8_t available_pairs, uint8_t *pairs) {
|
|
// keyno < 3: ULAES
|
|
// keyno = 3: ULC
|
|
mful_3passauth_t payload = {
|
|
.turn_off_field = switch_off_field,
|
|
.try_auth = try_auth,
|
|
.check_answer = check_answer,
|
|
.use_schann = schann,
|
|
.use_fastread0 = use_fastread0,
|
|
.get_nonces = get_nonces,
|
|
.reset_field = reset_field,
|
|
.keyno = keyno,
|
|
.retries = retries,
|
|
.available_pairs = available_pairs,
|
|
};
|
|
memcpy(payload.key, key, sizeof(payload.key));
|
|
int pairs_bytecount = (keyno == 3 ? 8 + 16 : 16 + 32) * MIN(available_pairs, keyno == 3 ? 10 : 5);
|
|
|
|
if (pairs_bytecount && pairs) {
|
|
memcpy(payload.pairs, pairs, pairs_bytecount);
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU3P_AUTH, (uint8_t *)&payload, sizeof(payload) - sizeof(payload.pairs) + pairs_bytecount);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU3P_AUTH, &resp, 1500 + (retries * 15)) == false) {
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
struct rp {
|
|
uint32_t auths;
|
|
uint32_t ticks;
|
|
uint8_t nonces[PM3_CMD_DATA_SIZE - sizeof(uint32_t) * 2];
|
|
} PACKED;
|
|
struct rp *rpayload = (struct rp *) resp.data.asBytes;
|
|
|
|
if (auths != NULL) {
|
|
*auths += rpayload->auths;
|
|
}
|
|
if (ms != NULL) {
|
|
*ms += rpayload->ticks;
|
|
}
|
|
if (get_nonces && nonces != NULL) {
|
|
memcpy(nonces, rpayload->nonces, MIN(sizeof(rpayload->nonces), rpayload->auths * (keyno == 3 ? 8 : 16)));
|
|
}
|
|
return resp.status;
|
|
}
|
|
|
|
static int trace_mfuc_try_key(uint8_t *key, int state, uint8_t (*authdata)[16]) {
|
|
uint8_t iv[8] = {0};
|
|
uint8_t RndB[8] = {0};
|
|
uint8_t RndARndB[16] = {0};
|
|
uint8_t RndA[8] = {0};
|
|
mbedtls_des3_context ctx_des3;
|
|
switch (state) {
|
|
case 2:
|
|
mbedtls_des3_set2key_dec(&ctx_des3, key);
|
|
mbedtls_des3_crypt_cbc(&ctx_des3, MBEDTLS_DES_DECRYPT,
|
|
8, iv, authdata[0], RndB);
|
|
mbedtls_des3_crypt_cbc(&ctx_des3, MBEDTLS_DES_DECRYPT,
|
|
16, iv, authdata[1], RndARndB);
|
|
if ((memcmp(&RndB[1], &RndARndB[8], 7) == 0) &&
|
|
(RndB[0] == RndARndB[15])) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
break;
|
|
case 3:
|
|
if (key == NULL) {// if no key was found
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(iv, authdata[0], 8);
|
|
mbedtls_des3_set2key_dec(&ctx_des3, key);
|
|
mbedtls_des3_crypt_cbc(&ctx_des3, MBEDTLS_DES_DECRYPT,
|
|
16, iv, authdata[1], RndARndB);
|
|
mbedtls_des3_crypt_cbc(&ctx_des3, MBEDTLS_DES_DECRYPT,
|
|
8, iv, authdata[2], RndA);
|
|
if ((memcmp(&RndARndB[1], RndA, 7) == 0) &&
|
|
(RndARndB[0] == RndA[7])) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
break;
|
|
default:
|
|
return PM3_EINVARG;
|
|
}
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
int trace_mfuc_try_default_3des_keys(uint8_t **correct_key, int state, uint8_t (*authdata)[16]) {
|
|
switch (state) {
|
|
case 2:
|
|
for (uint8_t i = 0; i < ARRAYLEN(default_3des_keys); ++i) {
|
|
uint8_t *key = default_3des_keys[i];
|
|
if (trace_mfuc_try_key(key, state, authdata) == PM3_SUCCESS) {
|
|
*correct_key = key;
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
return trace_mfuc_try_key(*correct_key, state, authdata);
|
|
break;
|
|
default:
|
|
return PM3_EINVARG;
|
|
}
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// param override, means we override hw debug levels.
|
|
static int try_default_3des_keys(bool override, uint8_t **correct_key, bool use_fastread0) {
|
|
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (override) {
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
}
|
|
int res = PM3_ESOFT;
|
|
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(SUCCESS, "--- " _CYAN_("Known UL-C 3DES keys"));
|
|
|
|
for (uint8_t i = 0; i < ARRAYLEN(default_3des_keys); ++i) {
|
|
uint8_t *key = default_3des_keys[i];
|
|
if (ul3pass_authentication(key, MIFAREULC_KEY_INDEX, true, 0, NULL, NULL, false, true, true, use_fastread0, false, NULL, false, 0, NULL) == PM3_SUCCESS) {
|
|
*correct_key = key;
|
|
res = PM3_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (override) {
|
|
setDeviceDebugLevel(dbg_curr, false);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// param override, means we override hw debug levels.
|
|
static int try_default_aes_keys(bool override, bool use_schann, bool use_fastread0) {
|
|
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (override) {
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
int res = PM3_ESOFT;
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "--- " _CYAN_("Known UL-AES keys"));
|
|
|
|
for (uint8_t i = 0; i < ARRAYLEN(default_aes_keys); ++i) {
|
|
uint8_t *key = default_aes_keys[i];
|
|
|
|
for (uint8_t keyno = 0; keyno < 3; keyno++) {
|
|
|
|
if (ul3pass_authentication(key, keyno, true, 0, NULL, NULL, use_schann, true, true, use_fastread0, false, NULL, false, 0, NULL) == PM3_SUCCESS) {
|
|
|
|
char keystr[20] = {0};
|
|
switch (keyno) {
|
|
case 0:
|
|
sprintf(keystr, "Data key");
|
|
break;
|
|
case 1:
|
|
sprintf(keystr, "UID key");
|
|
break;
|
|
case 2:
|
|
sprintf(keystr, "Authenticity key");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
PrintAndLogEx(SUCCESS, "%02X " _YELLOW_("%16s") " - %s ( "_GREEN_("ok") " )"
|
|
, keyno
|
|
, keystr
|
|
, sprint_hex_inrow(key, 16)
|
|
);
|
|
|
|
res = PM3_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (override) {
|
|
setDeviceDebugLevel(dbg_curr, false);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int ul_auth_select(iso14a_card_select_t *card, uint64_t tagtype, bool hasAuthKey, uint8_t *authkey, uint8_t *pack, uint8_t packSize, bool use_schann) {
|
|
if (ul_select(card) == false) {
|
|
return PM3_ESOFT;
|
|
}
|
|
bool use_fastread0 = false;
|
|
if (hasAuthKey && (tagtype & MFU_TT_UL_C)) {
|
|
//will select card automatically and close connection on error
|
|
if (ul3pass_authentication(authkey, MIFAREULC_KEY_INDEX, false, 0, NULL, NULL, false, true, true, use_fastread0, false, NULL, false, 0, NULL) != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Authentication Failed UL-C");
|
|
return PM3_ESOFT;
|
|
}
|
|
} else if (hasAuthKey && (tagtype & MFU_TT_UL_AES)) {
|
|
//will select card automatically and close connection on error
|
|
if (ul3pass_authentication(authkey, 0, false, 0, NULL, NULL, use_schann, true, true, use_fastread0, false, NULL, false, 0, NULL) != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Authentication Failed UL-AES");
|
|
return PM3_ESOFT;
|
|
}
|
|
} else {
|
|
if (hasAuthKey) {
|
|
if (ulev1_requestAuthentication(authkey, pack, packSize) == PM3_EWRONGANSWER) {
|
|
DropField();
|
|
PrintAndLogEx(WARNING, "Authentication Failed UL-EV1/NTAG");
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ntagtt_getTamperStatus(uint8_t *response, uint16_t responseLength) {
|
|
uint8_t cmd[] = {NTAGTT_CMD_READ_TT, 0x00};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, false);
|
|
}
|
|
|
|
static int ulev1_getVersion(uint8_t *response, uint16_t responseLength, bool schann) {
|
|
uint8_t cmd[] = {MIFARE_ULEV1_VERSION};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, schann);
|
|
}
|
|
|
|
static int ulev1_readCounter(uint8_t counter, uint8_t *response, uint16_t responseLength, bool schann) {
|
|
uint8_t cmd[] = {MIFARE_ULEV1_READ_CNT, counter};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, schann);
|
|
}
|
|
|
|
static int ulev1_readTearing(uint8_t counter, uint8_t *response, uint16_t responseLength) {
|
|
uint8_t cmd[] = {MIFARE_ULEV1_CHECKTEAR, counter};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, false);
|
|
}
|
|
|
|
static int ulev1_readSignature(uint8_t *response, uint16_t responseLength, bool schann) {
|
|
uint8_t cmd[] = {MIFARE_ULEV1_READSIG, 0x00};
|
|
return ul_send_cmd_raw(cmd, sizeof(cmd), response, responseLength, schann);
|
|
}
|
|
|
|
// Fudan check checks for which error is given for a command with incorrect crc
|
|
// NXP UL chip responds with 01, fudan 00.
|
|
// other possible checks:
|
|
// send a0 + crc
|
|
// UL responds with 00, fudan doesn't respond
|
|
// or
|
|
// send a200 + crc
|
|
// UL doesn't respond, fudan responds with 00
|
|
// or
|
|
// send 300000 + crc (read with extra byte(s))
|
|
// UL responds with read of page 0, fudan doesn't respond.
|
|
//
|
|
// make sure field is off before calling this function
|
|
static long long unsigned int ul_fudan_check(void) {
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card) == false) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
|
|
uint8_t cmd[4] = {ISO14443A_CMD_READBLOCK, 0x00, 0x02, 0xa7}; // wrong crc on purpose, should be 0xa8
|
|
clearCommandBuffer();
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_NO_DISCONNECT | ISO14A_NO_RATS, cmd, sizeof(cmd));
|
|
PacketResponseNG resp;
|
|
uint16_t rlen_810 = 0;
|
|
if (WaitForIso14aReply(&resp, 1500, &rlen_810, NULL) == false) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
if (rlen_810 != 1) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
|
|
return (resp.data.asBytes[0] == 0)
|
|
? MFU_TT_FUDAN_UL : MFU_TT_UL; //if response == 0x00 then Fudan, else Genuine NXP
|
|
}
|
|
|
|
static int ul_print_default(uint8_t *data, uint8_t *real_uid) {
|
|
|
|
uint8_t uid[7];
|
|
uid[0] = data[0];
|
|
uid[1] = data[1];
|
|
uid[2] = data[2];
|
|
uid[3] = data[4];
|
|
uid[4] = data[5];
|
|
uid[5] = data[6];
|
|
uid[6] = data[7];
|
|
bool mful_uid_layout = true;
|
|
|
|
if (memcmp(uid, real_uid, 7) != 0) {
|
|
mful_uid_layout = false;
|
|
}
|
|
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(real_uid, 7));
|
|
PrintAndLogEx(SUCCESS, " UID[0]: %02X, %s", real_uid[0], getTagInfo(real_uid[0]));
|
|
if (real_uid[0] == 0x05 && ((real_uid[1] & 0xf0) >> 4) == 2) { // is infineon and 66RxxP
|
|
uint8_t chip = (data[8] & 0xC7); // 11000111 mask, bit 3,4,5 RFU
|
|
switch (chip) {
|
|
case 0xC2:
|
|
PrintAndLogEx(SUCCESS, " IC type: SLE 66R04P 770 Bytes");
|
|
break; //77 pages
|
|
case 0xC4:
|
|
PrintAndLogEx(SUCCESS, " IC type: SLE 66R16P 2560 Bytes");
|
|
break; //256 pages
|
|
case 0xC6:
|
|
PrintAndLogEx(SUCCESS, " IC type: SLE 66R32P 5120 Bytes");
|
|
break; //512 pages /2 sectors
|
|
}
|
|
}
|
|
if (mful_uid_layout) {
|
|
// CT (cascade tag byte) 0x88 xor SN0 xor SN1 xor SN2
|
|
int crc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
|
|
if (data[3] == crc0)
|
|
PrintAndLogEx(SUCCESS, " BCC0: %02X ( " _GREEN_("ok") " )", data[3]);
|
|
else
|
|
PrintAndLogEx(NORMAL, " BCC0: %02X, crc should be %02X", data[3], crc0);
|
|
|
|
int crc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
|
|
if (data[8] == crc1)
|
|
PrintAndLogEx(SUCCESS, " BCC1: %02X ( " _GREEN_("ok") " )", data[8]);
|
|
else
|
|
PrintAndLogEx(NORMAL, " BCC1: %02X, crc should be %02X", data[8], crc1);
|
|
if (uid[0] == 0x04) {
|
|
PrintAndLogEx(SUCCESS, " Internal: %02X ( %s )", data[9], (data[9] == 0x48) ? _GREEN_("default") : _RED_("not default"));
|
|
} else if (uid[0] == 0x02) {
|
|
PrintAndLogEx(SUCCESS, " Sysblock: %02X ( %s )", data[9], (data[9] == 0x2C) ? _GREEN_("default") : _RED_("not default"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Internal: %02X", data[9]);
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Blocks 0-2: %s", sprint_hex(data + 0, 12));
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " Lock: %s - %s",
|
|
sprint_hex(data + 10, 2),
|
|
sprint_bin(data + 10, 2)
|
|
);
|
|
|
|
PrintAndLogEx(SUCCESS, " OTP: " _YELLOW_("%s") " - %s",
|
|
sprint_hex(data + 12, 4),
|
|
sprint_bin(data + 12, 4)
|
|
);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static bool ndef_detect_message(const uint8_t *d, uint16_t n) {
|
|
|
|
if (n < 17) {
|
|
return false;
|
|
}
|
|
|
|
// start at OTP block and detect a CC container instead
|
|
const uint8_t *p = d + (3 * MFU_BLOCK_SIZE);
|
|
|
|
// no NDEF capability container
|
|
if (p[0] != 0xE1 && p[0] != 0xF1) {
|
|
return false;
|
|
}
|
|
|
|
p += 4;
|
|
const uint8_t *end = d + n;
|
|
|
|
// empty data area
|
|
if (p[0] == 0x00) {
|
|
return false;
|
|
}
|
|
|
|
while (p < end) {
|
|
|
|
// NDEF terminator TLV (0xFE 0x00)
|
|
if (p[0] == 0xFE && (p + 1) < end && p[1] == 0x00) {
|
|
return true;
|
|
}
|
|
p++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Size of the NDEF data area, as announced by the Capability Container in block 3.
|
|
//
|
|
// CC[0] magic number, 0xE1 (0xF1 also accepted for NTAG I2C compatability)
|
|
// CC[1] mapping version and read/write access
|
|
// CC[2] MLEN, size of the data area expressed in units of 8 bytes
|
|
// CC[3] additional access conditions
|
|
//
|
|
// NFC Forum Type 2 Tag Operation defines the data area as 8 * MLEN bytes
|
|
// Returns the size in bytes, 0 when the CC announces no NDEF data area.
|
|
static uint16_t ndef_get_maxsize(const uint8_t *data) {
|
|
|
|
// no NDEF capability container
|
|
if (data[0] != 0xE1 && data[0] != 0xF1) {
|
|
return 0;
|
|
}
|
|
|
|
return (uint16_t)data[2] * 8;
|
|
}
|
|
|
|
static int ndef_print_CC(uint8_t *data) {
|
|
|
|
// no NDEF message
|
|
if (data[0] != 0xE1 && data[0] != 0xF1) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
//NFC Forum Type 1,2,3,4
|
|
//
|
|
// 4 has 1.1 (11)
|
|
|
|
// b7, b6 major version
|
|
// b5, b4 minor version
|
|
// b3, b2 read
|
|
// 00 always, 01 rfu, 10 proprietary, 11 rfu
|
|
// b1, b0 write
|
|
// 00 always, 01 rfo, 10 proprietary, 11 never
|
|
uint8_t cc_write = data[1] & 0x03;
|
|
uint8_t cc_read = (data[1] & 0x0C) >> 2;
|
|
uint8_t cc_minor = (data[1] & 0x30) >> 4;
|
|
uint8_t cc_major = (data[1] & 0xC0) >> 6;
|
|
|
|
const char *wStr;
|
|
switch (cc_write) {
|
|
case 0:
|
|
wStr = "Write access granted without any security";
|
|
break;
|
|
case 1:
|
|
wStr = "RFU";
|
|
break;
|
|
case 2:
|
|
wStr = "Proprietary";
|
|
break;
|
|
case 3:
|
|
wStr = "No write access";
|
|
break;
|
|
default:
|
|
wStr = "Unknown";
|
|
break;
|
|
}
|
|
const char *rStr;
|
|
switch (cc_read) {
|
|
case 0:
|
|
rStr = "Read access granted without any security";
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
rStr = "RFU";
|
|
break;
|
|
case 2:
|
|
rStr = "Proprietary";
|
|
break;
|
|
default:
|
|
rStr = "Unknown";
|
|
break;
|
|
}
|
|
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("NDEF Message"));
|
|
PrintAndLogEx(SUCCESS, "Capability Container: " _YELLOW_("%s"), sprint_hex_inrow(data, 4));
|
|
PrintAndLogEx(SUCCESS, " %02X: NDEF Magic Number", data[0]);
|
|
|
|
// PrintAndLogEx(SUCCESS, " %02X : version %d.%d supported by tag", data[1], (data[1] & 0xF0) >> 4, data[1] & 0x0F);
|
|
PrintAndLogEx(SUCCESS, " %02X: version %d.%d supported by tag", data[1], cc_major, cc_minor);
|
|
PrintAndLogEx(SUCCESS, " : %s / %s", rStr, wStr);
|
|
|
|
PrintAndLogEx(SUCCESS, " %02X: Physical Memory Size: %d bytes", data[2], data[2] * 8);
|
|
if (data[2] == 0x06)
|
|
PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 48);
|
|
else if (data[2] == 0x12)
|
|
PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 144);
|
|
else if (data[2] == 0x3E)
|
|
PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 496);
|
|
else if (data[2] == 0x6D)
|
|
PrintAndLogEx(SUCCESS, " %02X: NDEF Memory Size: %d bytes", data[2], 872);
|
|
|
|
uint8_t msb3 = (data[3] & 0xE0) >> 5;
|
|
uint8_t sf = (data[3] & 0x10) >> 4;
|
|
uint8_t lb = (data[3] & 0x08) >> 3;
|
|
uint8_t mlrule = (data[3] & 0x06) >> 1;
|
|
uint8_t mbread = (data[3] & 0x01);
|
|
|
|
PrintAndLogEx(SUCCESS, " %02X: Additional feature information", data[3]);
|
|
|
|
uint8_t bits[8 + 1] = {0};
|
|
num_to_bytebits(data[3], 8, bits);
|
|
const char *bs = sprint_bytebits_bin(bits, 8);
|
|
|
|
PrintAndLogEx(SUCCESS, " %s", bs);
|
|
if (msb3 == 0) {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 0, 3, "RFU"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_RED, bs, 8, 0, 3, "RFU"));
|
|
}
|
|
|
|
if (sf) {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 3, 1, "Support special frame"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 3, 1, "Don\'t support special frame"));
|
|
}
|
|
|
|
if (lb) {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 4, 1, "Support lock block"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 4, 1, "Don\'t support lock block"));
|
|
}
|
|
|
|
if (mlrule == 0) {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 5, 2, "RFU"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_RED, bs, 8, 5, 2, "RFU"));
|
|
}
|
|
|
|
if (mbread) {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 7, 1, "IC support multiple block reads"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " %s", sprint_breakdown_bin(C_NONE, bs, 8, 7, 1, "IC don\'t support multiple block reads"));
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int ul_print_type(uint64_t tagtype, uint8_t spaces) {
|
|
|
|
if (spaces > 10) {
|
|
spaces = 10;
|
|
}
|
|
|
|
char typestr[140];
|
|
memset(typestr, 0x00, sizeof(typestr));
|
|
|
|
if (tagtype & MFU_TT_UL)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight (MF0ICU1)"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_C)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight C (MF0ULC)"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_NANO_40)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight Nano 40bytes (MF0UNH00)"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_EV1_48)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight EV1 48bytes (MF0UL1101)"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_EV1_128)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight EV1 128bytes (MF0UL2101)"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_EV1)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight EV1 UNKNOWN"), spaces, "");
|
|
else if (tagtype & MFU_TT_UL_AES)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("MIFARE Ultralight AES"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG UNKNOWN"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_203)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 203 144bytes (NT2H0301F0DT)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_210u)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210u (micro) 48bytes (NT2L1001G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_210)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210 48bytes (NT2L1011G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_212)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 212 128bytes (NT2L1211G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_213)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 213 144bytes (NT2H1311G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_213_F)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 213F 144bytes (NT2H1311F0DTL)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_213_C)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 213C 144bytes (NT2H1311C1DTL)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_213_TT)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 213TT 144bytes (NT2H1311TTDU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_215)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 215 504bytes (NT2H1511G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_216)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 216 888bytes (NT2H1611G0DU)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_216_F)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 216F 888bytes (NT2H1611F0DTL)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_223_DNA)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 223 DNA 144bytes (NT2H2331G0)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_223_DNA_SD)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 223 DNA StatusDetect 144bytes (NT2H2331S0)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_224_DNA)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 224 DNA 208bytes (NT2H2421G0)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_224_DNA_SD)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 224 DNA StatusDetect 208bytes (NT2H2421S0)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_I2C_1K)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG I2C 888bytes (NT3H1101FHK)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_I2C_2K)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG I2C 1904bytes (NT3H1201FHK)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_I2C_1K_PLUS)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG I2C plus 888bytes (NT3H2111FHK)"), spaces, "");
|
|
else if (tagtype & MFU_TT_NTAG_I2C_2K_PLUS)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG I2C plus 1912bytes (NT3H2211FHK)"), spaces, "");
|
|
else if (tagtype & MFU_TT_MY_D)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("INFINEON my-d\x99 (SLE 66RxxS)"), spaces, "");
|
|
else if (tagtype & MFU_TT_MY_D_NFC)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("INFINEON my-d\x99 NFC (SLE 66RxxP)"), spaces, "");
|
|
else if (tagtype & MFU_TT_MY_D_MOVE)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("INFINEON my-d\x99 move (SLE 66R01P)"), spaces, "");
|
|
else if (tagtype & MFU_TT_MY_D_MOVE_NFC)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("INFINEON my-d\x99 move NFC (SLE 66R01P)"), spaces, "");
|
|
else if (tagtype & MFU_TT_MY_D_MOVE_LEAN)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("INFINEON my-d\x99 move lean (SLE 66R01L)"), spaces, "");
|
|
else if (tagtype & MFU_TT_FUDAN_UL)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("FUDAN Ultralight Compatible (or other compatible)"), spaces, "");
|
|
else if (tagtype & MFU_TT_ST25TN512)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("ST ST25TN512 64bytes"), spaces, "");
|
|
else if (tagtype & MFU_TT_ST25TN01K)
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("ST ST25TN01K 160bytes"), spaces, "");
|
|
else
|
|
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("Unknown %06" PRIx64), spaces, "", tagtype);
|
|
|
|
|
|
|
|
bool ismagic = ((tagtype & MFU_TT_MAGIC) == MFU_TT_MAGIC);
|
|
// clear magic flag
|
|
tagtype &= ~(MFU_TT_MAGIC);
|
|
|
|
if (ismagic) {
|
|
snprintf(typestr + strlen(typestr), 4, " ( ");
|
|
}
|
|
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_1A) == MFU_TT_MAGIC_1A) ? _GREEN_("Gen 1a") : "");
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_1B) == MFU_TT_MAGIC_1B) ? _GREEN_("Gen 1b") : "");
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_2) == MFU_TT_MAGIC_2) ? _GREEN_("Gen 2 / CUID") : "");
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_4) == MFU_TT_MAGIC_4) ? _GREEN_("USCUID-UL") : "");
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_NTAG) == MFU_TT_MAGIC_NTAG) ? _GREEN_("NTAG CUID") : "");
|
|
snprintf(typestr + strlen(typestr), sizeof(typestr) - strlen(typestr), "%s", ((tagtype & MFU_TT_MAGIC_NTAG21X) == MFU_TT_MAGIC_NTAG21X) ? _GREEN_("NTAG21x") : "");
|
|
|
|
|
|
if (ismagic) {
|
|
snprintf(typestr + strlen(typestr), 4, " )");
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "%s", typestr);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ulc_print_3deskey(uint8_t *data) {
|
|
PrintAndLogEx(INFO, " deskey1 [44/0x2C]: %s [%s]", sprint_hex(data, 4), sprint_ascii(data, 4));
|
|
PrintAndLogEx(INFO, " deskey1 [45/0x2D]: %s [%s]", sprint_hex(data + 4, 4), sprint_ascii(data + 4, 4));
|
|
PrintAndLogEx(INFO, " deskey2 [46/0x2E]: %s [%s]", sprint_hex(data + 8, 4), sprint_ascii(data + 8, 4));
|
|
PrintAndLogEx(INFO, " deskey2 [47/0x2F]: %s [%s]", sprint_hex(data + 12, 4), sprint_ascii(data + 12, 4));
|
|
PrintAndLogEx(INFO, "3des key: " _GREEN_("%s"), sprint_hex_inrow(SwapEndian64(data, 16, 8), 16));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// Only takes 16 bytes of data. Now key data available here
|
|
static int ulc_print_configuration(uint8_t *data) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("UL-C Configuration") " --------------------------");
|
|
PrintAndLogEx(INFO, "Total memory....... " _YELLOW_("%u") " bytes", MAX_ULC_BLOCKS * 4);
|
|
PrintAndLogEx(INFO, "Available memory... " _YELLOW_("%u") " bytes", (MAX_ULC_BLOCKS - 4) * 4);
|
|
PrintAndLogEx(INFO, "40 / 0x28 | %s - %s Higher lockbits", sprint_hex(data, 4), sprint_bin(data, 2));
|
|
PrintAndLogEx(INFO, "41 / 0x29 | %s - %s Counter", sprint_hex(data + 4, 4), sprint_bin(data + 4, 2));
|
|
|
|
bool validAuth = (data[8] >= 0x03 && data[8] < 0x30);
|
|
if (validAuth) {
|
|
PrintAndLogEx(INFO, "42 / 0x2A | %s Auth0 Page " _YELLOW_("%d") "/" _YELLOW_("0x%02X") " and above need authentication"
|
|
, sprint_hex(data + 8, 4)
|
|
, data[8]
|
|
, data[8]
|
|
);
|
|
} else {
|
|
if (data[8] == 0) {
|
|
PrintAndLogEx(INFO, "42 / 0x2A | %s Auth0 default", sprint_hex(data + 8, 4));
|
|
} else if (data[8] == 0x30) {
|
|
PrintAndLogEx(INFO, "42 / 0x2A | %s Auth0 " _GREEN_("unlocked"), sprint_hex(data + 8, 4));
|
|
} else {
|
|
PrintAndLogEx(INFO, "42 / 0x2A | %s Auth0 " _RED_("byte is out-of-range"), sprint_hex(data + 8, 4));
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "43 / 0x2B | %s Auth1 %s",
|
|
sprint_hex(data + 12, 4),
|
|
(data[12] & 1) ? "write access restricted" : _RED_("R/W access restricted")
|
|
);
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ulaes_print_configuration(uint8_t *data, uint8_t start_page) {
|
|
|
|
// first call
|
|
if (start_page == 0x2C) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("UL-AES Configuration") " --------------------------");
|
|
|
|
bool rid_act = (data[0] & 0x01);
|
|
bool sec_msg_act = (data[0] & 0x02);
|
|
bool prot = (data[4] & 0x80);
|
|
bool cfglck = (data[4] & 0x40);
|
|
bool cnt_inc_en = (data[4] & 0x08);
|
|
bool cnt_rd_en = (data[4] & 0x04);
|
|
uint16_t authlim = (data[6]) | ((data[7] & 0x3) << 8);
|
|
|
|
PrintAndLogEx(INFO, " cfg0 [%u/0x%02X] " _YELLOW_("%s"), start_page, start_page, sprint_hex_inrow(data, 4));
|
|
|
|
PrintAndLogEx(INFO, " - Random ID is %s", (rid_act) ? _RED_("enabled") : _GREEN_("disabled"));
|
|
PrintAndLogEx(INFO, " - Secure messaging is %s", (sec_msg_act) ? _RED_("enabled") : _RED_("disabled"));
|
|
if (data[3] < 0x3c) {
|
|
PrintAndLogEx(INFO, " - page " _YELLOW_("%d") " and above need authentication", data[3]);
|
|
} else {
|
|
PrintAndLogEx(INFO, " - pages don't need authentication");
|
|
}
|
|
start_page++;
|
|
|
|
PrintAndLogEx(INFO, " cfg1 [%u/0x%02X] " _YELLOW_("%s"), start_page, start_page, sprint_hex_inrow(data + 4, 4));
|
|
|
|
if (authlim == 0) {
|
|
PrintAndLogEx(INFO, " - " _GREEN_("Unlimited authentication attempts"));
|
|
} else {
|
|
PrintAndLogEx(INFO, " - Max number of authentication attempts is " _YELLOW_("%d"), authlim);
|
|
}
|
|
PrintAndLogEx(INFO, " - %s access requires authentication", (prot) ? _RED_("Read and write") : "Write");
|
|
PrintAndLogEx(INFO, " - User configuration is %s", (cfglck) ? _RED_("locked") : _GREEN_("unlocked"));
|
|
PrintAndLogEx(INFO, " - Counter 2 increment access %s authentication", (cnt_inc_en) ? _GREEN_("does not require") : _RED_("requires"));
|
|
PrintAndLogEx(INFO, " - Counter 2 read access %s authentication", (cnt_rd_en) ? _GREEN_("does not require") : _RED_("requires"));
|
|
start_page++;
|
|
|
|
PrintAndLogEx(INFO, " RFU [%u/0x%02X] " _YELLOW_("%s"), start_page, start_page, sprint_hex_inrow(data + 8, 4));
|
|
start_page++;
|
|
|
|
PrintAndLogEx(INFO, " RFU [%u/0x%02X] " _YELLOW_("%s"), start_page, start_page, sprint_hex_inrow(data + 12, 4));
|
|
} else if (start_page == 0x2D) {
|
|
|
|
bool lck_aes1 = (data[0] & 0x80);
|
|
bool lck_aes0 = (data[0] & 0x40);
|
|
bool block_lck = (data[0] & 0x20);
|
|
PrintAndLogEx(INFO, " CMAC cfg [%u/0x%02X] " _YELLOW_("%s"), start_page, start_page, sprint_hex_inrow(data, 4));
|
|
PrintAndLogEx(INFO, " - AESKey 1 is %s", (lck_aes1) ? _RED_("locked") : _GREEN_("unlocked"));
|
|
PrintAndLogEx(INFO, " - AESKey 0 is %s", (lck_aes0) ? _RED_("locked") : _GREEN_("unlocked"));
|
|
PrintAndLogEx(INFO, " - Block lock key cfg is %s", (block_lck) ? _RED_("perma locked") : _GREEN_("unlocked"));
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ulev1_print_configuration(uint64_t tagtype, uint8_t *data, uint8_t startPage) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Configuration"));
|
|
|
|
bool strg_mod_en = (data[0] & 0x04);
|
|
uint8_t authlim = (data[4] & 0x07);
|
|
bool nfc_cnf_prot_pwd = ((data[4] & 0x08) == 0x08);
|
|
bool nfc_cnf_en = ((data[4] & 0x10) == 0x10);
|
|
bool cfglck = ((data[4] & 0x40) == 0x40);
|
|
bool prot = ((data[4] & 0x80) == 0x80);
|
|
|
|
uint8_t vctid = data[5];
|
|
|
|
PrintAndLogEx(INFO, " cfg0 [%u/0x%02X]: " _YELLOW_("%s"), startPage, startPage, sprint_hex_inrow(data, 4));
|
|
|
|
//NTAG213TT has different ASCII mirroring options and config bytes interpretation from other ulev1 class tags
|
|
if (tagtype & MFU_TT_NTAG_213_TT) {
|
|
uint8_t mirror_conf = ((data[0] & 0xE0) >> 5);
|
|
uint8_t mirror_byte = ((data[0] & 0x18) >> 3);
|
|
uint8_t mirror_page = data[2];
|
|
|
|
switch (mirror_conf) {
|
|
case 0:
|
|
PrintAndLogEx(INFO, " - no ASCII mirror");
|
|
break;
|
|
case 1:
|
|
PrintAndLogEx(INFO, " - UID ASCII mirror");
|
|
break;
|
|
case 2:
|
|
PrintAndLogEx(INFO, " - NFC counter ASCII mirror");
|
|
break;
|
|
case 3:
|
|
PrintAndLogEx(INFO, " - UID and NFC counter ASCII mirror");
|
|
break;
|
|
case 4:
|
|
PrintAndLogEx(INFO, " - tag tamper ASCII mirror");
|
|
break;
|
|
case 5:
|
|
PrintAndLogEx(INFO, " - UID and tag tamper ASCII mirror");
|
|
break;
|
|
case 6:
|
|
PrintAndLogEx(INFO, " - NFC counter and tag tamper ASCII mirror");
|
|
break;
|
|
case 7:
|
|
PrintAndLogEx(INFO, " - UID, NFC counter, and tag tamper ASCII mirror");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (mirror_conf) {
|
|
uint8_t mirror_user_mem_start_byte = (4 * (mirror_page - 4)) + mirror_byte;
|
|
uint8_t bytes_required_for_mirror_data = 0;
|
|
|
|
switch (mirror_conf) {
|
|
case 1:
|
|
bytes_required_for_mirror_data = 14;
|
|
break;
|
|
case 2:
|
|
bytes_required_for_mirror_data = 6;
|
|
break;
|
|
case 3:
|
|
bytes_required_for_mirror_data = 8;
|
|
break;
|
|
case 4:
|
|
bytes_required_for_mirror_data = 21;
|
|
break;
|
|
case 5:
|
|
bytes_required_for_mirror_data = 23;
|
|
break;
|
|
case 6:
|
|
bytes_required_for_mirror_data = 15;
|
|
break;
|
|
case 7:
|
|
bytes_required_for_mirror_data = 30;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
PrintAndLogEx(INFO, " mirror start page %02X | byte pos %02X - %s"
|
|
, mirror_page, mirror_byte
|
|
, (mirror_page >= 0x4 && ((mirror_user_mem_start_byte + bytes_required_for_mirror_data) <= 144)) ? _GREEN_("ok") : _YELLOW_("Invalid value")
|
|
);
|
|
}
|
|
|
|
} else if (tagtype & (MFU_TT_NTAG_213_F | MFU_TT_NTAG_216_F)) {
|
|
uint8_t mirror_conf = ((data[0] & 0xC0) >> 6);
|
|
uint8_t mirror_byte = (data[0] & 0x30);
|
|
bool sleep_en = (data[0] & 0x08);
|
|
strg_mod_en = (data[0] & 0x04);
|
|
uint8_t fdp_conf = (data[0] & 0x03);
|
|
|
|
switch (mirror_conf) {
|
|
case 0:
|
|
PrintAndLogEx(INFO, " - no ASCII mirror");
|
|
break;
|
|
case 1:
|
|
PrintAndLogEx(INFO, " - UID ASCII mirror");
|
|
break;
|
|
case 2:
|
|
PrintAndLogEx(INFO, " - NFC counter ASCII mirror");
|
|
break;
|
|
case 3:
|
|
PrintAndLogEx(INFO, " - UID and NFC counter ASCII mirror");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, " - SLEEP mode %s", (sleep_en) ? "enabled" : "disabled");
|
|
|
|
switch (fdp_conf) {
|
|
case 0:
|
|
PrintAndLogEx(INFO, " - no field detect");
|
|
break;
|
|
case 1:
|
|
PrintAndLogEx(INFO, " - enabled by first State-of-Frame (start of communication)");
|
|
break;
|
|
case 2:
|
|
PrintAndLogEx(INFO, " - enabled by selection of the tag");
|
|
break;
|
|
case 3:
|
|
PrintAndLogEx(INFO, " - enabled by field presence");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// valid mirror start page and byte position within start page.
|
|
if (tagtype & MFU_TT_NTAG_213_F) {
|
|
switch (mirror_conf) {
|
|
case 1:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0x24) ? "OK" : "Invalid value"); break;}
|
|
case 2:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0x26) ? "OK" : "Invalid value"); break;}
|
|
case 3:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0x22) ? "OK" : "Invalid value"); break;}
|
|
default:
|
|
break;
|
|
}
|
|
} else if (tagtype & MFU_TT_NTAG_216_F) {
|
|
switch (mirror_conf) {
|
|
case 1:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0xDE) ? "OK" : "Invalid value"); break;}
|
|
case 2:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0xE0) ? "OK" : "Invalid value"); break;}
|
|
case 3:
|
|
{ PrintAndLogEx(INFO, " mirror start block %02X | byte pos %02X - %s", data[2], mirror_byte, (data[2] >= 0x4 && data[2] <= 0xDC) ? "OK" : "Invalid value"); break;}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
PrintAndLogEx(INFO, " - strong modulation mode %s", (strg_mod_en) ? "enabled" : "disabled");
|
|
|
|
if (data[3] < 0xff)
|
|
PrintAndLogEx(INFO, " - page %d and above need authentication", data[3]);
|
|
else
|
|
PrintAndLogEx(INFO, " - pages don't need authentication");
|
|
|
|
uint8_t tt_enabled = 0;
|
|
uint8_t tt_message[4] = {0x00};
|
|
uint8_t tt_msg_resp_len = 0;
|
|
uint8_t tt_status_resp[5] = {0x00};
|
|
|
|
if (tagtype & MFU_TT_NTAG_213_TT) {
|
|
tt_enabled = (data[1] & 0x02);
|
|
tt_msg_resp_len = ul_read(45, tt_message, 4, false);
|
|
|
|
PrintAndLogEx(INFO, " - tamper detection feature is %s"
|
|
, (tt_enabled) ? _GREEN_("ENABLED") : "disabled"
|
|
);
|
|
|
|
switch (data[1] & 0x06) {
|
|
case 0x00:
|
|
PrintAndLogEx(INFO, " - tamper message is unlocked and read/write enabled");
|
|
break;
|
|
case 0x02:
|
|
PrintAndLogEx(INFO, " - tamper message is reversibly read/write locked in memory while the tamper feature is enabled");
|
|
break;
|
|
case 0x04:
|
|
case 0x06:
|
|
PrintAndLogEx(INFO, " - tamper message is permanently read/write locked in memory");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, " cfg1 [%u/0x%02X]: " _YELLOW_("%s"), startPage + 1, startPage + 1, sprint_hex_inrow(data + 4, 4));
|
|
if (authlim == 0)
|
|
PrintAndLogEx(INFO, " - " _GREEN_("Unlimited password attempts"));
|
|
else
|
|
PrintAndLogEx(INFO, " - Max number of password attempts is " _YELLOW_("%d"), authlim);
|
|
|
|
PrintAndLogEx(INFO, " - NFC counter %s", (nfc_cnf_en) ? "enabled" : "disabled");
|
|
PrintAndLogEx(INFO, " - NFC counter %s", (nfc_cnf_prot_pwd) ? "password protection enabled" : "not protected");
|
|
|
|
PrintAndLogEx(INFO, " - user configuration %s", cfglck ? "permanently locked" : "writeable");
|
|
PrintAndLogEx(INFO, " - %s access is protected with password", prot ? "read and write" : "write");
|
|
PrintAndLogEx(INFO, " - %02X, Virtual Card Type Identifier is %sdefault", vctid, (vctid == 0x05) ? "" : "not ");
|
|
PrintAndLogEx(INFO, " PWD [%u/0x%02X]: %s ( cannot be read )", startPage + 2, startPage + 2, sprint_hex_inrow(data + 8, 4));
|
|
PrintAndLogEx(INFO, " PACK [%u/0x%02X]: %s ( cannot be read )", startPage + 3, startPage + 3, sprint_hex_inrow(data + 12, 2));
|
|
PrintAndLogEx(INFO, " RFU [%u/0x%02X]: %s ( cannot be read )", startPage + 3, startPage + 3, sprint_hex_inrow(data + 14, 2));
|
|
|
|
if (tagtype & MFU_TT_NTAG_213_TT) {
|
|
if (data[1] & 0x06) {
|
|
PrintAndLogEx(INFO, "TT_MSG [45/0x2D]: %s (cannot be read)", sprint_hex_inrow(tt_message, tt_msg_resp_len));
|
|
PrintAndLogEx(INFO, " - tamper message is masked in memory");
|
|
} else {
|
|
PrintAndLogEx(INFO, "TT_MSG [45/0x2D]: %s", sprint_hex_inrow(tt_message, tt_msg_resp_len));
|
|
PrintAndLogEx(INFO, " - tamper message is %s and is readable/writablbe in memory", sprint_hex(tt_message, tt_msg_resp_len));
|
|
}
|
|
}
|
|
|
|
//The NTAG213TT only returns meaningful information for the fields below if the tamper feature is enabled
|
|
if ((tagtype & MFU_TT_NTAG_213_TT) && tt_enabled) {
|
|
|
|
int tt_status_len = ntagtt_getTamperStatus(tt_status_resp, 5);
|
|
if (tt_status_len != 5) {
|
|
PrintAndLogEx(WARNING, "Error sending the READ_TT_STATUS command to tag\n");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tamper Status"));
|
|
PrintAndLogEx(INFO, " READ_TT_STATUS: %s", sprint_hex_inrow(tt_status_resp, 5));
|
|
|
|
PrintAndLogEx(INFO, " Tamper status result from this power-up:");
|
|
switch (tt_status_resp[4]) {
|
|
case 0x43:
|
|
PrintAndLogEx(INFO, " - Tamper loop was detcted as closed during this power-up");
|
|
break;
|
|
case 0x4F:
|
|
PrintAndLogEx(INFO, " - Tamper loop was detected as open during this power-up");
|
|
break;
|
|
case 0x49:
|
|
PrintAndLogEx(INFO, " - Tamper loop measurement was not enabled or not valid during this power-up");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, " Tamper detection permanent memory:");
|
|
if ((tt_status_resp[0] | tt_status_resp [1] | tt_status_resp[2] | tt_status_resp[3]) == 0x00)
|
|
|
|
PrintAndLogEx(INFO, " - Tamper loop has never been detected as open during power-up");
|
|
else {
|
|
PrintAndLogEx(INFO, " - Tamper loop was detected as open during power-up at least once");
|
|
PrintAndLogEx(INFO, " - Tamper message returned by READ_TT_STATUS command: %s", sprint_hex(tt_status_resp, 4));
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ulev1_print_counters(uint64_t tagtype, bool use_schann) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Counters"));
|
|
uint8_t tear[1] = {0};
|
|
uint8_t counter[3] = {0, 0, 0};
|
|
int len = 0;
|
|
for (uint8_t i = 0; i < 3; ++i) {
|
|
len = ulev1_readCounter(i, counter, sizeof(counter), use_schann);
|
|
if (len == 3) {
|
|
PrintAndLogEx(INFO, " [%0d]: %s", i, sprint_hex(counter, 3));
|
|
if ((tagtype & MFU_TT_UL_AES) != MFU_TT_UL_AES) {
|
|
ulev1_readTearing(i, tear, sizeof(tear));
|
|
PrintAndLogEx(SUCCESS, " - %02X tearing ( %s )"
|
|
, tear[0]
|
|
, (tear[0] == 0xBD) ? _GREEN_("ok") : _RED_("fail")
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int ulev1_print_signature(uint64_t tagtype, uint8_t *uid, uint8_t *signature, size_t signature_len) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Signature"));
|
|
int index = -1;
|
|
if (signature_len == 32) {
|
|
index = originality_check_verify(uid, 7, signature, signature_len, PK_MFUL);
|
|
} else if (signature_len == 48) {
|
|
index = originality_check_verify(uid, 7, signature, signature_len, PK_MFULAES);
|
|
}
|
|
return originality_check_print(signature, signature_len, index);
|
|
}
|
|
|
|
static int ulev1_print_version(uint8_t *data) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Version"));
|
|
PrintAndLogEx(INFO, " Raw bytes: " _YELLOW_("%s"), sprint_hex_inrow(data, 8));
|
|
PrintAndLogEx(INFO, " Vendor ID: %02X, %s", data[1], getTagInfo(data[1]));
|
|
PrintAndLogEx(INFO, " Product type: %s", getProductTypeStr(data[2]));
|
|
PrintAndLogEx(INFO, " Product subtype: %02X, %s", data[3], (data[3] == 1) ? "17 pF" : "50pF");
|
|
PrintAndLogEx(INFO, " Major version: %02X", data[4]);
|
|
PrintAndLogEx(INFO, " Minor version: %02X", data[5]);
|
|
PrintAndLogEx(INFO, " Size: %s", getUlev1CardSizeStr(data[6]));
|
|
PrintAndLogEx(INFO, " Protocol type: %02X%s", data[7], (data[7] == 0x3) ? ", ISO14443-3 Compliant" : "");
|
|
|
|
if (memcmp(data, "\x00\x04\x03\x03\x04\x00\x0F\x03", 8) == 0) {
|
|
PrintAndLogEx(INFO, _RED_("Send copy to iceman of this command output!"));
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int ntag_print_counter(void) {
|
|
// NTAG has one counter. At address 0x02. With no tearing.
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Counter"));
|
|
uint8_t counter[3] = {0, 0, 0};
|
|
uint16_t len;
|
|
len = ulev1_readCounter(0x02, counter, sizeof(counter), false);
|
|
PrintAndLogEx(INFO, " [02]: %s", sprint_hex(counter, 3));
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
static int ulc_magic_test(){
|
|
// Magic Ultralight test
|
|
// Magic UL-C, by observation,
|
|
// 1) it seems to have a static nonce response to 0x1A command.
|
|
// 2) the deskey bytes is not-zero:d out on as datasheet states.
|
|
// 3) UID - changeable, not only, but pages 0-1-2-3.
|
|
// 4) use the ul_magic_test ! magic tags answers specially!
|
|
int returnValue = UL_ERROR;
|
|
iso14a_card_select_t card;
|
|
uint8_t nonce1[11] = {0x00};
|
|
uint8_t nonce2[11] = {0x00};
|
|
if ( !ul_select(&card) ){
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
int status = ulc_requestAuthentication(nonce1, sizeof(nonce1));
|
|
if ( status <= 0 ) {
|
|
status = ulc_requestAuthentication(nonce2, sizeof(nonce2));
|
|
returnValue = ( !memcmp(nonce1, nonce2, 11) ) ? MFU_TT_UL_C_MAGIC : MFU_TT_UL_C;
|
|
} else {
|
|
returnValue = MFU_TT_UL;
|
|
}
|
|
DropField();
|
|
return returnValue;
|
|
}
|
|
*/
|
|
static uint64_t ul_magic_test(void) {
|
|
// Magic Ultralight tests
|
|
// 1) take present UID, and try to write it back. OBSOLETE
|
|
// 2) make a wrong length write to page0, and see if tag answers with ACK/NACK:
|
|
|
|
DropField();
|
|
|
|
iso14a_card_select_t card;
|
|
if (ul_select_rats(&card) == false) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
|
|
/*
|
|
// iceman: how to proper identify RU based UID cards
|
|
if (
|
|
(memcmp(card.uid, "\xAA\x55\x39", 3) == 0) ||
|
|
(memcmp(card.uid, "\xAA\x55\xC3", 3) == 0)
|
|
) {
|
|
// Ul-5 MFU Ev1 FUID,
|
|
return MFU_TT_UL_EV1_MAGIC;
|
|
}
|
|
*/
|
|
PrintAndLogEx(DEBUG, "%u - %s", card.ats_len, sprint_hex_inrow(card.ats, card.ats_len));
|
|
|
|
// USCUID-UL cards
|
|
if (card.ats_len == 18) {
|
|
|
|
// USCUID-UL configuration
|
|
// https://github.com/RfidResearchGroup/proxmark3/blob/master/doc/magic_cards_notes.md#uscuid-ul-configuration-guide
|
|
// identify: ATS len 18,
|
|
// First 8 bytes can vary depending on setup. next 8 bytes is GET VERSION data and finally 2 byte crc
|
|
//
|
|
// \x85\x00\x00\xA0\x0A\x00\x0A\xC3 \x00\x04\x03\x01\x01\x00\x0B\x03 \xZZ\xZZ
|
|
//
|
|
// 7AFF - back door enabled
|
|
// 8500 -
|
|
// if we ignore first 8 bytes we can identify regardless how card is configured
|
|
//
|
|
if (compare_ul_family(card.ats + 8, 8)) {
|
|
return MFU_TT_MAGIC_4 | MFU_TT_MAGIC;
|
|
}
|
|
}
|
|
|
|
// Direct write alternative cards
|
|
if (card.ats_len == 14) {
|
|
|
|
// UL Direct Write , UL-C Direct write, NTAG 213 Direct write
|
|
if (memcmp(card.ats, "\x0A\x78\x00\x81\x02\xDB\xA0\xC1\x19\x40\x2A\xB5", 12) == 0) {
|
|
return MFU_TT_MAGIC_2;
|
|
}
|
|
}
|
|
|
|
|
|
int status = ul_comp_write(0, NULL, 0, false);
|
|
DropField();
|
|
if (status == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "comp write pass");
|
|
return MFU_TT_MAGIC_2 | MFU_TT_MAGIC;
|
|
}
|
|
|
|
// check for GEN1A, GEN1B and NTAG21x
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
mf_chinese_ident_t payload = {
|
|
.is_mfc = false,
|
|
.keytype = MF_KEY_A,
|
|
};
|
|
SendCommandNG(CMD_HF_MIFARE_CIDENT, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
uint16_t is_generation = MAGIC_FLAG_NONE;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFARE_CIDENT, &resp, 1500)) {
|
|
if ((resp.status == PM3_SUCCESS) && resp.length == sizeof(uint16_t)) {
|
|
is_generation = resp.data.asDwords[0] & 0xFFFF;
|
|
}
|
|
}
|
|
|
|
if ((is_generation & MAGIC_FLAG_GEN_1A) == MAGIC_FLAG_GEN_1A) {
|
|
return MFU_TT_MAGIC_1A | MFU_TT_MAGIC;
|
|
}
|
|
|
|
if ((is_generation & MAGIC_FLAG_GEN_1B) == MAGIC_FLAG_GEN_1B) {
|
|
return MFU_TT_MAGIC_1B | MFU_TT_MAGIC;
|
|
}
|
|
|
|
if ((is_generation & MAGIC_FLAG_NTAG21X) == MAGIC_FLAG_NTAG21X) {
|
|
return MFU_TT_MAGIC_NTAG21X | MFU_TT_MAGIC;
|
|
}
|
|
|
|
return MFU_TT_UNKNOWN;
|
|
}
|
|
|
|
static char *mfu_generate_filename(const char *prefix, const char *suffix) {
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(WARNING, "No tag found.");
|
|
return NULL;
|
|
}
|
|
|
|
char *fptr = calloc(sizeof(char) * (strlen(prefix) + strlen(suffix)) + sizeof(card.uid) * 2 + 1, sizeof(uint8_t));
|
|
if (fptr == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return NULL;
|
|
}
|
|
strcpy(fptr, prefix);
|
|
FillFileNameByUID(fptr, card.uid, suffix, card.uidlen);
|
|
return fptr;
|
|
}
|
|
|
|
// used with the Amiibo dumps loading...
|
|
// Not related to 'hf mfu dump'
|
|
static int mfu_dump_tag(uint16_t pages, void **pdata, uint16_t *len, bool use_schann) {
|
|
|
|
// read uid
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card) == false) {
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
int res = PM3_SUCCESS;
|
|
uint16_t maxbytes = (pages * MFU_BLOCK_SIZE);
|
|
|
|
*pdata = calloc(maxbytes, sizeof(uint8_t));
|
|
if (*pdata == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
res = PM3_EMALLOC;
|
|
goto out;
|
|
}
|
|
|
|
// read card
|
|
mful_readblock_t packet = {
|
|
.block_no = 0,
|
|
.num_of_blocks = 4,
|
|
.keytype = 2, // UL_EV1/NTAG auth
|
|
.keylen = 4,
|
|
.use_schann = use_schann,
|
|
};
|
|
|
|
// generate PWD
|
|
num_to_bytes(ul_ev1_pwdgenB(card.uid), 4, packet.key);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READCARD, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_READCARD, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
free(*pdata);
|
|
*pdata = NULL;
|
|
res = PM3_ETIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Failed reading card");
|
|
free(*pdata);
|
|
*pdata = NULL;
|
|
res = resp.status;
|
|
goto out;
|
|
}
|
|
|
|
// read all memory
|
|
mful_readblock_resp_t *payload = (mful_readblock_resp_t *)resp.data.asBytes;
|
|
uint32_t startindex = payload->startidx;
|
|
uint32_t buffer_size = payload->bytelen;
|
|
|
|
if (buffer_size > maxbytes) {
|
|
PrintAndLogEx(FAILED, "Data exceeded buffer size!");
|
|
buffer_size = maxbytes;
|
|
}
|
|
|
|
if (GetFromDevice(BIG_BUF, *pdata, buffer_size, startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
free(*pdata);
|
|
*pdata = NULL;
|
|
res = PM3_ETIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (len) {
|
|
*len = buffer_size;
|
|
}
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
Lego Dimensions,
|
|
Version: 00 04 04 02 01 00 0F 03
|
|
|
|
matching bytes:
|
|
index 12 ( 3 * 4 )
|
|
E1 10 12 00 01 03 A0 0C 34 03 13 D1 01 0F 54 02 65 6E
|
|
*/
|
|
|
|
typedef struct {
|
|
const char *desc;
|
|
uint8_t mpos;
|
|
uint8_t mlen;
|
|
const char *match;
|
|
uint32_t (*otp)(const uint8_t *uid);
|
|
const char *hint;
|
|
} mfu_otp_identify_t;
|
|
|
|
static mfu_otp_identify_t mfu_otp_ident_table[] = {
|
|
{ "SALTO Systems card", 12, 4, "534C544F", ul_c_otpgenA, "report to iceman!" },
|
|
{ NULL, 0, 0, NULL, NULL, NULL} // must be the last item
|
|
};
|
|
|
|
static mfu_otp_identify_t *mfu_match_otp_fingerprint(uint8_t *uid, uint8_t *data) {
|
|
uint8_t i = 0;
|
|
do {
|
|
if (mfu_otp_ident_table[i].desc == NULL) {
|
|
break;
|
|
}
|
|
int ml = 0;
|
|
uint8_t mtmp[40] = {0};
|
|
|
|
// static or dynamic created OTP to fingerprint.
|
|
if (mfu_otp_ident_table[i].match) {
|
|
param_gethex_to_eol(mfu_otp_ident_table[i].match, 0, mtmp, sizeof(mtmp), &ml);
|
|
} else {
|
|
uint32_t otp = mfu_otp_ident_table[i].otp(uid);
|
|
num_to_bytes(otp, 4, mtmp);
|
|
}
|
|
|
|
int min = MIN(mfu_otp_ident_table[i].mlen, 4);
|
|
|
|
PrintAndLogEx(DEBUG, "uid.... %s", sprint_hex_inrow(uid, 7));
|
|
PrintAndLogEx(DEBUG, "calc... %s", sprint_hex_inrow(mtmp, 4));
|
|
PrintAndLogEx(DEBUG, "dump... %s", sprint_hex_inrow(data + mfu_otp_ident_table[i].mpos, min));
|
|
|
|
bool m2 = (memcmp(mtmp, data + mfu_otp_ident_table[i].mpos, min) == 0);
|
|
if (m2) {
|
|
PrintAndLogEx(DEBUG, "(fingerprint) found %s", mfu_otp_ident_table[i].desc);
|
|
return &mfu_otp_ident_table[i];
|
|
}
|
|
} while (++i < ARRAYLEN(mfu_otp_ident_table));
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct {
|
|
const char *desc;
|
|
const char *version;
|
|
uint8_t mpos;
|
|
uint8_t mlen;
|
|
const char *match;
|
|
uint32_t (*Pwd)(const uint8_t *uid);
|
|
uint16_t (*Pack)(const uint8_t *uid);
|
|
const char *hint;
|
|
} mfu_identify_t;
|
|
|
|
static mfu_identify_t mfu_ident_table[] = {
|
|
{
|
|
"Jooki", "0004040201000F03",
|
|
12, 32, "E11012000103A00C340329D101255504732E6A6F6F6B692E726F636B732F732F",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
"hf mfu ndefread"
|
|
},
|
|
{
|
|
"Lego Dimensions", "0004040201000F03",
|
|
12, 18, "E11012000103A00C340313D1010F5402656E",
|
|
ul_ev1_pwdgenC, ul_ev1_packgenC,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
{
|
|
"Hotwheels", "0004040201000F03",
|
|
9, 9, "E110120F",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
{
|
|
"Minecraft Earth", "0004040201000F03",
|
|
9, 26, "48F6FFE1101200037C91012C55027069642E6D617474656C2F4167",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
{
|
|
"Snackworld", "0004040101000B03",
|
|
9, 7, "483000E1100600",
|
|
NULL, NULL,
|
|
"hf mfu dump -k"
|
|
},
|
|
{
|
|
"Amiibo", "0004040201001103",
|
|
9, 9, "480FE0F110FFEEA500",
|
|
ul_ev1_pwdgenB, ul_ev1_packgenB,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
{
|
|
"Amiibo - Power Up band", "0004040502021303",
|
|
8, 10, "44000FE0F110FFEEA500",
|
|
ul_ev1_pwdgenB, ul_ev1_packgenB,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
/*
|
|
{
|
|
"Xiaomi AIR Purifier", "0004040201000F03",
|
|
0, 0, "",
|
|
ul_ev1_pwdgenE, ul_ev1_packgenE,
|
|
"hf mfu dump -k %08x"
|
|
},
|
|
*/
|
|
{
|
|
"Philips Toothbrush", "0004040201010F03",
|
|
16, 20, "0310D1010C55027068696C6970732E636F6DFE00",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
"hf mfu pwdgen -r"
|
|
},
|
|
{
|
|
"Philips Toothbrush", "0004040201010F03",
|
|
16, 36, "0320D1011C55027068696C6970732E636F6D2F6E6663627275736868656164746170FE00",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
"hf mfu pwdgen -r"
|
|
},
|
|
{
|
|
"Bank Of Archie brothers", "0004030101000B03",
|
|
9, 11, "48F6FF0000000036343533",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
NULL
|
|
},
|
|
{
|
|
"Art-Dass NFT card", "0004040201000F03",
|
|
16, 16, "033ED1013A5504617274646173732E6E",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
NULL
|
|
},
|
|
{
|
|
"Bonverde Coffe card", "0004030101000B03",
|
|
18, 4, "644B05AA",
|
|
ul_ev1_pwdgen_def, ul_ev1_packgen_def,
|
|
NULL
|
|
},
|
|
{NULL, NULL, 0, 0, NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
static mfu_identify_t *mfu_match_fingerprint(const uint8_t *version, const uint8_t *data) {
|
|
uint8_t i = 0;
|
|
do {
|
|
|
|
int vl = 0;
|
|
uint8_t vtmp[10] = {0};
|
|
param_gethex_to_eol(mfu_ident_table[i].version, 0, vtmp, sizeof(vtmp), &vl);
|
|
|
|
bool m1 = (memcmp(vtmp, version, vl) == 0);
|
|
if (m1 == false) {
|
|
PrintAndLogEx(DEBUG, "(fingerprint) wrong version");
|
|
continue;
|
|
}
|
|
|
|
int ml = 0;
|
|
uint8_t mtmp[40] = {0};
|
|
param_gethex_to_eol(mfu_ident_table[i].match, 0, mtmp, sizeof(mtmp), &ml);
|
|
|
|
bool m2 = (memcmp(mtmp, data + mfu_ident_table[i].mpos, mfu_ident_table[i].mlen) == 0);
|
|
if (m2) {
|
|
PrintAndLogEx(DEBUG, "(fingerprint) found %s", mfu_ident_table[i].desc);
|
|
return &mfu_ident_table[i];
|
|
}
|
|
} while (mfu_ident_table[++i].desc);
|
|
return NULL;
|
|
}
|
|
|
|
static uint8_t mfu_max_len(void) {
|
|
uint8_t n = 0, i = 0;
|
|
do {
|
|
uint8_t tmp = mfu_ident_table[i].mpos + mfu_ident_table[i].mlen;
|
|
if (tmp > n) {
|
|
n = tmp;
|
|
}
|
|
} while (mfu_ident_table[++i].desc);
|
|
return n;
|
|
}
|
|
|
|
int mfu_get_version_uid(uint8_t *version, uint8_t *uid) {
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card) == false) {
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(uid, card.uid, card.uidlen);
|
|
|
|
uint8_t v[10] = {0x00};
|
|
int len = ulev1_getVersion(v, sizeof(v), false);
|
|
DropField();
|
|
if (len != sizeof(v)) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
memcpy(version, v, 8);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int mfulc_fingerprint(void) {
|
|
iso14a_card_select_t card;
|
|
PacketResponseNG resp;
|
|
|
|
// Old LAB401 ULC DW
|
|
// To be checked before FJ8010
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(ERR, "Unable to select tag");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
uint8_t cmd0[] = {0xAF};
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS, cmd0, sizeof(cmd0));
|
|
uint16_t rlen_2017 = 0;
|
|
if (WaitForIso14aReply(&resp, 500, &rlen_2017, NULL)) {
|
|
if ((rlen_2017 == 11) && (resp.data.asBytes[0] == 0x00)) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Lab401 Ultralight-C compatible UID modifiable"));
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
DropField();
|
|
|
|
// Feiju FJ8010
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(ERR, "Unable to select tag");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
uint8_t cmd1[] = {0x1A, 0x2F};
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS, cmd1, sizeof(cmd1));
|
|
uint16_t rlen_2035 = 0;
|
|
if (WaitForIso14aReply(&resp, 500, &rlen_2035, NULL)) {
|
|
if ((rlen_2035 == 11) && (resp.data.asBytes[0] == 0xAF)) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Feiju FJ8010"));
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
DropField();
|
|
|
|
// USCUID-UL with ULC authentication
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(ERR, "Unable to select tag");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
uint8_t cmd2[] = {0x1A};
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_NO_RATS, cmd2, sizeof(cmd2));
|
|
uint16_t rlen_2053 = 0;
|
|
if (WaitForIso14aReply(&resp, 500, &rlen_2053, NULL)) {
|
|
if ((rlen_2053 == 11) && (resp.data.asBytes[0] == 0xAF)) {
|
|
uint8_t response[11] = {0};
|
|
memcpy(response, resp.data.asBytes, 9);
|
|
compute_crc(CRC_14443_A, response, 9, response + 9, response + 10);
|
|
response[9] ^= resp.data.asBytes[9];
|
|
response[10] ^= resp.data.asBytes[10];
|
|
if ((response[9] == 0x6C) && (response[10] == 0xF3)) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("USCUID-UL with ULC authentication, variant 1"));
|
|
} else if ((response[9] == 0xB4) && (response[10] == 0xC5)) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("USCUID-UL with ULC authentication, variant 2"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("USCUID-UL with ULC authentication") _RED_(" unknown variant") ", please report!");
|
|
}
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
DropField();
|
|
|
|
// GT23SC4489
|
|
uint8_t cmd3a[] = {0x26};
|
|
uint8_t cmd3b[] = {0x30};
|
|
// 7 bit REQA, so lenbits carries it and len stays 0
|
|
SendIso14aReaderEx(ISO14A_RAW | ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_SELECT | ISO14A_NO_DISCONNECT
|
|
, cmd3a, sizeof(cmd3a), 0, 7, 0, 0);
|
|
uint16_t rlen_2080 = 0;
|
|
if (WaitForIso14aReply(&resp, 500, &rlen_2080, NULL)) {
|
|
if (rlen_2080 == 2) {
|
|
SendIso14aReader(ISO14A_RAW | ISO14A_NO_SELECT, cmd3b, sizeof(cmd3b));
|
|
uint16_t rlen_2084 = 0;
|
|
if (WaitForIso14aReply(&resp, 500, &rlen_2084, NULL)) {
|
|
if (rlen_2084 == 18) {
|
|
if ((resp.data.asBytes[0] == 0x04) && (resp.data.asBytes[6] == 0x15) && (resp.data.asBytes[7] == 0x89)) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("GT23SC4489"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("GT23SC4489") _RED_(" unknown variant") ", please report!");
|
|
}
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DropField();
|
|
|
|
// Mimicking TagInfo to identify MIFARE Hospitality cards: blk2[2]=09 and blk3=E1101200
|
|
if (ul_select(&card)) {
|
|
uint8_t data[4] = { 0x00 };
|
|
uint8_t cmd[] = { ISO14443A_CMD_READBLOCK, 2 };
|
|
int status = ul_send_cmd_raw(cmd, sizeof(cmd), data, 4, false);
|
|
if ((status > 0) && (data[2] == 0x09)) {
|
|
cmd[1] = 3;
|
|
status = ul_send_cmd_raw(cmd, sizeof(cmd), data, 4, false);
|
|
if ((status > 0) && (data[0] == 0xE1) && (data[1] == 0x10) && (data[2] == 0x12) && (data[3] == 0x00)) {
|
|
PrintAndLogEx(INFO, "MIFARE Hospitality (MF0ICU2(H))");
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
PrintAndLogEx(INFO, "likely MF0ICU2");
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int mfu_fingerprint(uint64_t tagtype, bool has_auth_key, const uint8_t *authkey, int ak_len, bool use_schann) {
|
|
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
uint8_t *data = NULL;
|
|
int res = PM3_ESOFT;
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));
|
|
|
|
// ULC fingerprinting
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
res = mfulc_fingerprint();
|
|
}
|
|
|
|
uint8_t maxbytes = mfu_max_len();
|
|
if (maxbytes == 0) {
|
|
PrintAndLogEx(ERR, "fingerprint table wrong");
|
|
res = PM3_ESOFT;
|
|
goto out;
|
|
}
|
|
|
|
maxbytes = ((maxbytes / MFU_BLOCK_SIZE) + 1) * MFU_BLOCK_SIZE;
|
|
data = calloc(maxbytes, sizeof(uint8_t));
|
|
if (data == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
res = PM3_EMALLOC;
|
|
goto out;
|
|
}
|
|
|
|
uint8_t pages = (maxbytes / MFU_BLOCK_SIZE);
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)
|
|
keytype = 1; // UL_C auth
|
|
else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)
|
|
keytype = 3; // UL_AES auth
|
|
else
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
res = PM3_ESOFT;
|
|
goto out;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
res = PM3_ESOFT;
|
|
goto out;
|
|
}
|
|
|
|
// read card
|
|
mful_readblock_t packet = {
|
|
.block_no = 0,
|
|
.num_of_blocks = pages,
|
|
.keytype = keytype,
|
|
.keylen = ak_len,
|
|
.use_schann = use_schann,
|
|
};
|
|
memcpy(packet.key, authkey, ak_len);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READCARD, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_READCARD, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
res = PM3_ETIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Failed reading card");
|
|
res = resp.status;
|
|
goto out;
|
|
}
|
|
|
|
// read all memory
|
|
mful_readblock_resp_t *payload = (mful_readblock_resp_t *)resp.data.asBytes;
|
|
uint32_t startindex = payload->startidx;
|
|
|
|
uint32_t buffer_size = payload->bytelen;
|
|
if (buffer_size > maxbytes) {
|
|
PrintAndLogEx(FAILED, "Data exceeded buffer size!");
|
|
buffer_size = maxbytes;
|
|
}
|
|
|
|
if (GetFromDevice(BIG_BUF, data, buffer_size, startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
res = PM3_ETIMEOUT;
|
|
goto out;
|
|
}
|
|
|
|
uint8_t version[8] = {0};
|
|
uint8_t uid[7] = {0};
|
|
if (mfu_get_version_uid(version, uid) == PM3_SUCCESS) {
|
|
mfu_identify_t *item = mfu_match_fingerprint(version, data);
|
|
if (item) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("%s"), item->desc);
|
|
res = PM3_SUCCESS;
|
|
|
|
if (item->hint) {
|
|
if (item->Pwd) {
|
|
char s[40] = {0};
|
|
snprintf(s, sizeof(s), item->hint, item->Pwd(uid));
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("%s") "`", s);
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("%s") "`", item->hint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// OTP checks
|
|
mfu_otp_identify_t *item = mfu_match_otp_fingerprint(uid, data);
|
|
if (item) {
|
|
PrintAndLogEx(SUCCESS, _BACK_GREEN_(" %s "), item->desc);
|
|
res = PM3_SUCCESS;
|
|
|
|
if (item->hint) {
|
|
if (item->otp) {
|
|
char s[40] = {0};
|
|
snprintf(s, sizeof(s), item->hint, item->otp(uid));
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("%s") "`", s);
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("%s") "`", item->hint);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "n/a");
|
|
}
|
|
|
|
setDeviceDebugLevel(dbg_curr, false);
|
|
free(data);
|
|
return res;
|
|
}
|
|
|
|
static int mfu_write_block(const uint8_t *data, uint8_t datalen, uint8_t keytype, const uint8_t *auth_key_ptr, uint8_t blockno, bool use_schann) {
|
|
|
|
mful_writeblock_t packet = {
|
|
.block_no = blockno,
|
|
.keytype = keytype,
|
|
.use_schann = use_schann,
|
|
.keylen = 0,
|
|
};
|
|
memcpy(packet.data, data, datalen);
|
|
|
|
// 0 - no pwd/key, no authentication
|
|
// 1 - 3des key (16 bytes)
|
|
// 2 - pwd (4 bytes)
|
|
// 3 - AES key (16 bytes)
|
|
if ((keytype == 1) || (keytype == 3)) {
|
|
memcpy(packet.key, auth_key_ptr, 16);
|
|
packet.keylen = 16;
|
|
} else if (keytype == 2) {
|
|
memcpy(packet.key, auth_key_ptr, 4);
|
|
packet.keylen = 4;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
|
|
if (datalen == 16) {
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL_COMPAT, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL_COMPAT, &resp, 1500) == false) {
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
} else {
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
}
|
|
return resp.status;
|
|
}
|
|
|
|
uint64_t GetHF14AMfU_Type(void) {
|
|
|
|
uint64_t tagtype = MFU_TT_UNKNOWN;
|
|
iso14a_card_select_t card;
|
|
|
|
if (ul_select(&card) == false)
|
|
return MFU_TT_UL_ERROR;
|
|
|
|
// Ultralight - ATQA / SAK
|
|
if (card.atqa[1] != 0x00 || card.sak != 0x00) {
|
|
//PrintAndLogEx(NORMAL, "Tag is not Ultralight | NTAG | MY-D |ST25TN [ATQA: %02X %02X SAK: %02X]\n", card.atqa[1], card.atqa[0], card.sak);
|
|
DropField();
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
if ((card.uid[0] == 0x02) && (card.atqa[0] == 0x44)) {
|
|
// ST25TN
|
|
// read SYSBLOCK
|
|
uint8_t data[4] = {0x00};
|
|
int status = ul_read(0x02, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
tagtype = MFU_TT_UL;
|
|
} else {
|
|
status = ul_read(data[1] + 1, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
tagtype = MFU_TT_UL;
|
|
} else {
|
|
// data[3] == KID == 0x05 Key ID
|
|
// data[2] == REV == 0x13 Product version
|
|
if ((data[1] == 0x90) && (data[0] == 0x90)) {
|
|
tagtype = MFU_TT_ST25TN01K;
|
|
} else if ((data[1] == 0x90) && (data[0] == 0x91)) {
|
|
tagtype = MFU_TT_ST25TN512;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if ((card.uid[0] == 0x05) && (card.atqa[0] == 0x44)) {
|
|
// Infineon MY-D tests Exam high nibble
|
|
DropField();
|
|
uint8_t nib = (card.uid[1] & 0xf0) >> 4;
|
|
switch (nib) {
|
|
// case 0: tagtype = SLE66R35E7; break; //or SLE 66R35E7 - mifare compat... should have different sak/atqa for mf 1k
|
|
case 1:
|
|
tagtype = MFU_TT_MY_D;
|
|
break; // or SLE 66RxxS ... up to 512 pages of 8 user bytes...
|
|
case 2:
|
|
tagtype = MFU_TT_MY_D_NFC;
|
|
break; // or SLE 66RxxP ... up to 512 pages of 8 user bytes... (or in nfc mode FF pages of 4 bytes)
|
|
case 3:
|
|
tagtype = (MFU_TT_MY_D_MOVE | MFU_TT_MY_D_MOVE_NFC);
|
|
break; // or SLE 66R01P // 38 pages of 4 bytes //notice: we can not currently distinguish between these two
|
|
case 7:
|
|
tagtype = MFU_TT_MY_D_MOVE_LEAN;
|
|
break; // or SLE 66R01L // 16 pages of 4 bytes
|
|
}
|
|
|
|
} else {
|
|
// Note that SAK might be 0x44 but also e.g. 0x04 for cards in Random ID mode
|
|
uint8_t version[10] = {0x00};
|
|
int len = ulev1_getVersion(version, sizeof(version), false);
|
|
DropField();
|
|
|
|
switch (len) {
|
|
case 0x0A: {
|
|
/*
|
|
MF0UL1001DUx 0004030100000B03
|
|
MF0UL1101DUx 0004030101000B03
|
|
MF0ULH1101DUx 0004030201000B03
|
|
MF0UL1141DUF 0004030301000B03
|
|
MF0UL2101Dxy 0004030101000E03
|
|
MF0UL2101DUx 0004030201000E03
|
|
MF0UL3101DUx 0004030101001103
|
|
MF0ULH3101DUx 0004030201001103
|
|
MF0UL5101DUx 0004030101001303
|
|
NT2L1011F0DUx 0004040101000B03
|
|
NT2H1011G0DUD 0004040201000B03
|
|
NT2L1211F0DUx 0004040101000E03
|
|
NT2H1311G0DUx 0004040201000F03
|
|
NT2H1311F0Dxy 0004040401000F03
|
|
NT2H1411G0DUx 0004040201011103
|
|
NT2H1511G0DUx 0004040201001103
|
|
NT2H1511F0Dxy 0004040401001103
|
|
NT2H1611G0DUx 0004040201001303
|
|
NT2H1611F0Dxy 0004040401001303
|
|
NT2H1311C1DTL 0004040201010F03
|
|
NT2H1311TTDUx 0004040203000F03
|
|
NT3H1101W0FHK 0004040502001303
|
|
NT3H1201W0FHK 0004040502001503
|
|
NT3H1101W0FHK_Variant 0004040502011303
|
|
NT3H1201 0004040502011503
|
|
NT3H2111 0004040502021303
|
|
NT3H2211 0004040502021503
|
|
nhs 0004040600001303
|
|
MF0UN0001DUx 0004030102000B03
|
|
MF0UNH0001DUx 0004030202000B03
|
|
MF0UN1001DUx 0004030103000B03
|
|
MF0UNH1001DUx 0004030203000B03
|
|
NT2L1001G0DUx 0004040102000B03
|
|
NT2H1001G0DUx 0004040202000B03
|
|
NT2H1311TTDUx 0004040203000F03
|
|
MF0AES2001DUD 0004030104000F03 17pF
|
|
0004030204000F03 50pF
|
|
0004030304000F03 75pF
|
|
|
|
Micron UL 0034210101000E03
|
|
Feiju NTAG 0053040201000F03
|
|
Feiju NTAG 215 0005340201001103
|
|
*/
|
|
|
|
if (memcmp(version, "\x00\x04\x03\x01\x01\x00\x0B", 7) == 0) { tagtype = MFU_TT_UL_EV1_48; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x01\x02\x00\x0B", 7) == 0) { tagtype = MFU_TT_UL_NANO_40; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x02\x01\x00\x0B", 7) == 0) { tagtype = MFU_TT_UL_EV1_48; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x01\x01\x00\x0E", 7) == 0) { tagtype = MFU_TT_UL_EV1_128; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x02\x01\x00\x0E", 7) == 0) { tagtype = MFU_TT_UL_EV1_128; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x01\x04\x00\x0F\x03", 8) == 0) { tagtype = MFU_TT_UL_AES; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x02\x04\x00\x0F\x03", 8) == 0) { tagtype = MFU_TT_UL_AES; break; }
|
|
else if (memcmp(version, "\x00\x04\x03\x03\x04\x00\x0F\x03", 8) == 0) { tagtype = MFU_TT_UL_AES; break; }
|
|
else if (memcmp(version, "\x00\x34\x21\x01\x01\x00\x0E", 7) == 0) { tagtype = MFU_TT_UL_EV1_128; break; } // Mikron JSC Russia EV1 41 pages tag
|
|
else if (memcmp(version, "\x00\x04\x04\x01\x01\x00\x0B", 7) == 0) { tagtype = MFU_TT_NTAG_210; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x01\x02\x00\x0B", 7) == 0) { tagtype = MFU_TT_NTAG_210u; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x02\x00\x0B", 7) == 0) { tagtype = MFU_TT_NTAG_210u; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x01\x01\x00\x0E", 7) == 0) { tagtype = MFU_TT_NTAG_212; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x01\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_213; break; }
|
|
else if (memcmp(version, "\x00\x53\x04\x02\x01\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_213; break; } // Shanghai Feiju Microelectronics Co. Ltd. China (Xiaomi Air Purifier filter)
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x01\x01\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_213_C; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x01\x00\x11", 7) == 0) { tagtype = MFU_TT_NTAG_215; break; }
|
|
else if (memcmp(version, "\x00\x05\x34\x02\x01\x00\x11", 7) == 0) { tagtype = MFU_TT_NTAG_215; break; } // Shanghai Feiju Microelectronics Co. Ltd. China
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x01\x00\x13", 7) == 0) { tagtype = MFU_TT_NTAG_216; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x04\x01\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_213_F; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x04\x01\x00\x13", 7) == 0) { tagtype = MFU_TT_NTAG_216_F; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x03\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_213_TT; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x04\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_223_DNA; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x08\x04\x00\x0F", 7) == 0) { tagtype = MFU_TT_NTAG_223_DNA_SD; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x02\x05\x00\x10", 7) == 0) { tagtype = MFU_TT_NTAG_224_DNA; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x08\x05\x00\x10", 7) == 0) { tagtype = MFU_TT_NTAG_224_DNA_SD; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x05\x02\x01\x13", 7) == 0) { tagtype = MFU_TT_NTAG_I2C_1K; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x05\x02\x01\x15", 7) == 0) { tagtype = MFU_TT_NTAG_I2C_2K; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x05\x02\x02\x13", 7) == 0) { tagtype = MFU_TT_NTAG_I2C_1K_PLUS; break; }
|
|
else if (memcmp(version, "\x00\x04\x04\x05\x02\x02\x15", 7) == 0) { tagtype = MFU_TT_NTAG_I2C_2K_PLUS; break; }
|
|
else if (version[2] == 0x04) { tagtype = MFU_TT_NTAG; break; }
|
|
else if (version[2] == 0x03) { tagtype = MFU_TT_UL_EV1; }
|
|
break;
|
|
}
|
|
case 0x01:
|
|
tagtype = MFU_TT_UL_C;
|
|
break;
|
|
case 0x00:
|
|
tagtype = MFU_TT_UL;
|
|
break;
|
|
case PM3_ETIMEOUT:
|
|
case PM3_EWRONGANSWER:
|
|
tagtype = (MFU_TT_UL | MFU_TT_UL_C | MFU_TT_NTAG_203);
|
|
break; // could be UL | UL_C magic tags
|
|
default :
|
|
tagtype = MFU_TT_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
// This is a test from cards that doesn't answer to GET_VERSION command
|
|
// UL vs UL-C vs NTAG203 vs FUDAN FM11NT021 (which is NTAG213 compatiable)
|
|
if (tagtype & (MFU_TT_UL | MFU_TT_UL_C | MFU_TT_NTAG_203)) {
|
|
if (ul_select(&card) == false) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
|
|
// do UL_C check first...
|
|
uint8_t nonce[11] = {0x00};
|
|
int status = ulc_requestAuthentication(nonce, sizeof(nonce));
|
|
DropField();
|
|
if (status > 1) {
|
|
tagtype = MFU_TT_UL_C;
|
|
} else {
|
|
// need to re-select after authentication error
|
|
if (ul_select(&card) == false) {
|
|
return MFU_TT_UL_ERROR;
|
|
}
|
|
|
|
uint8_t data[16] = {0x00};
|
|
|
|
// read page 0x26-0x29 (last valid ntag203 page)
|
|
// if error response, its ULTRALIGHT since doesn't have that memory block
|
|
status = ul_read(0x26, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
tagtype = MFU_TT_UL;
|
|
} else {
|
|
|
|
// read page 44 / 0x2C
|
|
// if error response, its NTAG203 since doesn't have that memory block
|
|
status = ul_read(0x2C, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
tagtype = MFU_TT_NTAG_203;
|
|
} else {
|
|
|
|
// read page 48 / 0x30
|
|
// if response, its FUDAN FM11NT021
|
|
status = ul_read(0x30, data, sizeof(data), false);
|
|
if (status == sizeof(data)) {
|
|
tagtype = MFU_TT_NTAG_213;
|
|
} else {
|
|
tagtype = MFU_TT_UNKNOWN;
|
|
}
|
|
}
|
|
}
|
|
DropField();
|
|
}
|
|
}
|
|
|
|
if (tagtype & MFU_TT_UL) {
|
|
tagtype = ul_fudan_check();
|
|
DropField();
|
|
}
|
|
}
|
|
|
|
tagtype |= ul_magic_test();
|
|
if (tagtype == (MFU_TT_UNKNOWN | MFU_TT_MAGIC)) {
|
|
tagtype = (MFU_TT_UL_MAGIC);
|
|
}
|
|
|
|
return tagtype;
|
|
}
|
|
//
|
|
// extended tag information
|
|
//
|
|
static int CmdHF14AMfUInfo(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu info",
|
|
"Get info about MIFARE Ultralight Family styled tag.\n"
|
|
"Sometimes the tags are locked down, and you may need a key to be able to read the information",
|
|
"hf mfu info\n"
|
|
"hf mfu info -k AABBCCDD\n"
|
|
"hf mfu info --key 00112233445566778899AABBCCDDEEFF"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-C 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0(NULL, "force", "override `hw dbg` settings"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
// arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
bool override = (arg_get_lit(ctx, 3) == false);
|
|
bool use_schann = arg_get_lit(ctx, 4);
|
|
// bool verbose = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
if (ak_len) {
|
|
if (ak_len != 16 && ak_len != 4) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
bool has_auth_key = false;
|
|
if (ak_len > 0) {
|
|
has_auth_key = true;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t authlim = 0xff;
|
|
uint8_t data[16] = {0x00};
|
|
iso14a_card_select_t card;
|
|
int status;
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
uint8_t pwd[4] = {0, 0, 0, 0};
|
|
uint8_t *key = pwd;
|
|
uint8_t pack[4] = {0, 0, 0, 0};
|
|
int len;
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " --------------------------");
|
|
ul_print_type(tagtype, 6);
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
bool locked = false;
|
|
// read pages 0,1,2,3 (should read 4 pages)
|
|
status = ul_read(0, data, sizeof(data), use_schann);
|
|
if (status <= 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ");
|
|
return PM3_ESOFT;
|
|
} else if (status == 16) {
|
|
ul_print_default(data, card.uid);
|
|
ndef_print_CC(data + 12);
|
|
} else {
|
|
locked = true;
|
|
}
|
|
|
|
// NXP specific
|
|
if ((tagtype & (MFU_TT_UL | MFU_TT_UL_C | MFU_TT_UL_EV1_48 | MFU_TT_UL_EV1_128 | MFU_TT_UL_EV1 | MFU_TT_UL_NANO_40 |
|
|
MFU_TT_NTAG | MFU_TT_NTAG_203 | MFU_TT_NTAG_210 | MFU_TT_NTAG_210u | MFU_TT_NTAG_212 |
|
|
MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C |
|
|
MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216 | MFU_TT_NTAG_216_F |
|
|
MFU_TT_NTAG_223_DNA | MFU_TT_NTAG_223_DNA_SD | MFU_TT_NTAG_224_DNA | MFU_TT_NTAG_224_DNA_SD |
|
|
MFU_TT_NTAG_I2C_1K | MFU_TT_NTAG_I2C_2K | MFU_TT_NTAG_I2C_1K_PLUS | MFU_TT_NTAG_I2C_2K_PLUS |
|
|
MFU_TT_UL_AES)) &&
|
|
((tagtype & (MFU_TT_MAGIC | MFU_TT_MAGIC_1A | MFU_TT_MAGIC_1B | MFU_TT_MAGIC_NTAG |
|
|
MFU_TT_MAGIC_2 | MFU_TT_MAGIC_4 | MFU_TT_MAGIC_4_GDM | MFU_TT_MAGIC_NTAG21X)) == 0)) {
|
|
// print silicon info
|
|
ul_print_nxp_silicon_info(card.uid);
|
|
}
|
|
|
|
// UL_C Specific
|
|
if ((tagtype & MFU_TT_UL_C)) {
|
|
|
|
// read pages 0x28, 0x29, 0x2A, 0x2B
|
|
uint8_t ulc_conf[16] = {0x00};
|
|
status = ul_read(0x28, ulc_conf, sizeof(ulc_conf), false);
|
|
if (status <= 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to page 40 read command");
|
|
PrintAndLogEx(HINT, "Hint: Tag config may be set to read-protect those pages, try dumping");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (status == 16) {
|
|
ulc_print_configuration(ulc_conf);
|
|
} else {
|
|
locked = true;
|
|
}
|
|
|
|
mfu_fingerprint(tagtype, has_auth_key, auth_key_ptr, ak_len, use_schann);
|
|
|
|
DropField();
|
|
|
|
if ((tagtype & MFU_TT_MAGIC) == MFU_TT_MAGIC) {
|
|
//just read key
|
|
uint8_t ulc_deskey[16] = {0x00};
|
|
if (ul_select(&card) == false) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Unable to select tag");
|
|
return PM3_ESOFT;
|
|
}
|
|
status = ul_read(0x2C, ulc_deskey, sizeof(ulc_deskey), false);
|
|
DropField();
|
|
if (status <= 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ magic");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (status == 16) {
|
|
PrintAndLogEx(SUCCESS, "Reading 3des key from magic card: ");
|
|
ulc_print_3deskey(ulc_deskey);
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
|
|
} else {
|
|
// if we called info with key, just return
|
|
if (has_auth_key) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// also try to diversify default keys.. look into CmdHF14AMfGenDiverseKeys
|
|
if (try_default_3des_keys(override, &key, false) == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Found default 3des key: ");
|
|
uint8_t keySwap[16];
|
|
memcpy(keySwap, SwapEndian64(key, 16, 8), 16);
|
|
ulc_print_3deskey(keySwap);
|
|
} else {
|
|
PrintAndLogEx(INFO, "n/a");
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// do counters and signature first (don't need auth)
|
|
|
|
// ul counters are different than ntag counters
|
|
if ((tagtype & (MFU_TT_UL_EV1_48 | MFU_TT_UL_EV1_128 | MFU_TT_UL_EV1 | MFU_TT_UL_AES))) {
|
|
if (ulev1_print_counters(tagtype, use_schann) != 3) {
|
|
// failed - re-select
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NTAG counters?
|
|
if ((tagtype & (MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C | MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216))) {
|
|
if (ntag_print_counter()) {
|
|
// failed - re-select
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), false) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ST25TN info & signature
|
|
if (tagtype & (MFU_TT_ST25TN512 | MFU_TT_ST25TN01K)) {
|
|
status = ul_read(0x02, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ SYSBLOCK");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
status = ul_read(data[1] + 1, data, sizeof(data), false);
|
|
if (status <= 1) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ SYSBLOCK");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag System Information"));
|
|
PrintAndLogEx(INFO, " Key ID: %02x", data[3]);
|
|
PrintAndLogEx(INFO, " Product Version: %02x", data[2]);
|
|
PrintAndLogEx(INFO, " Product Code: %02x%02x", data[1], data[0]);
|
|
uint8_t signature[32] = {0};
|
|
for (int blkoff = 0; blkoff < 8; blkoff++) {
|
|
status = ul_read(0x34 + blkoff, signature + (blkoff * 4), 4, false);
|
|
if (status <= 1) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ SYSBLOCK");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
// check signature
|
|
int index = originality_check_verify_ex(card.uid, 7, signature, sizeof(signature), PK_ST25TN, false, true);
|
|
originality_check_print(signature, sizeof(signature), index);
|
|
}
|
|
|
|
// Read signature
|
|
if ((tagtype & (MFU_TT_UL_EV1_48 | MFU_TT_UL_EV1_128 | MFU_TT_UL_EV1 | MFU_TT_UL_NANO_40 |
|
|
MFU_TT_NTAG_210u | MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C |
|
|
MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216 | MFU_TT_NTAG_216_F |
|
|
MFU_TT_NTAG_223_DNA | MFU_TT_NTAG_223_DNA_SD | MFU_TT_NTAG_224_DNA | MFU_TT_NTAG_224_DNA_SD |
|
|
MFU_TT_NTAG_I2C_1K | MFU_TT_NTAG_I2C_2K | MFU_TT_NTAG_I2C_1K_PLUS | MFU_TT_NTAG_I2C_2K_PLUS |
|
|
MFU_TT_UL_AES))) {
|
|
|
|
uint8_t ulev1_signature[48] = {0x00};
|
|
status = ulev1_readSignature(ulev1_signature, sizeof(ulev1_signature), use_schann);
|
|
if (status < 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ SIGNATURE");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
if (status == 32 || status == 34) {
|
|
ulev1_print_signature(tagtype, card.uid, ulev1_signature, 32);
|
|
} else if (status == 48) {
|
|
ulev1_print_signature(tagtype, card.uid, ulev1_signature, 48);
|
|
} else {
|
|
// re-select
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
// Get Version
|
|
uint8_t version[10] = {0x00};
|
|
status = ulev1_getVersion(version, sizeof(version), use_schann);
|
|
if (status < 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to GETVERSION");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
} else if (status == 10) {
|
|
ulev1_print_version(version);
|
|
} else {
|
|
locked = true;
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
uint8_t startconfigblock = 0;
|
|
uint8_t ulev1_conf[16] = {0x00};
|
|
|
|
for (uint8_t i = 1; i < ARRAYLEN(UL_TYPES_ARRAY); i++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[i]) == UL_TYPES_ARRAY[i]) {
|
|
startconfigblock = UL_MEMORY_ARRAY[i] - 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (startconfigblock) { // if we know where the config block is...
|
|
status = ul_read(startconfigblock, ulev1_conf, sizeof(ulev1_conf), use_schann);
|
|
if (status <= 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ EV1");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
} else if (status == 16) {
|
|
// save AUTHENTICATION LIMITS for later:
|
|
authlim = (ulev1_conf[4] & 0x07);
|
|
// add pwd / pack if used from cli
|
|
if (has_auth_key) {
|
|
memcpy(ulev1_conf + 8, auth_key_ptr, 4);
|
|
memcpy(ulev1_conf + 12, pack, 2);
|
|
}
|
|
ulev1_print_configuration(tagtype, ulev1_conf, startconfigblock);
|
|
}
|
|
}
|
|
|
|
// Only check extended config and skip passwords for Ul AES
|
|
if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
// read pages 0x28, (lock bytes) - we are skipping this block
|
|
// read pages 0x29, 0x2A, 0x2B, 0x2C (cfg1, cfg2, RFU, RFU)
|
|
uint8_t ulaes_conf[16] = {0x00};
|
|
status = ul_read(0x29, ulaes_conf, sizeof(ulaes_conf), use_schann);
|
|
if (status == 16) {
|
|
ulaes_print_configuration(ulaes_conf, 0x29);
|
|
|
|
memset(ulaes_conf, 0, sizeof(ulaes_conf));
|
|
// read page 0x2D, (CMAC CFG)
|
|
status = ul_read(0x2D, ulaes_conf, sizeof(ulaes_conf), use_schann);
|
|
if (status == 16) {
|
|
ulaes_print_configuration(ulaes_conf, 0x2D);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Warning: block 0x2D cannot be read");
|
|
locked = true;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Warning: block 0x29 cannot be read");
|
|
locked = true;
|
|
}
|
|
|
|
DropField();
|
|
|
|
if (ak_len != 16) {
|
|
// also try to diversify default keys.. look into CmdHF14AMfGenDiverseKeys
|
|
if (try_default_aes_keys(override, use_schann, false) != PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "n/a");
|
|
}
|
|
DropField();
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
|
|
// AUTHLIMIT, (number of failed authentications)
|
|
// 0 = limitless.
|
|
// 1-7 = limit. No automatic tries then.
|
|
// hasAuthKey, if we was called with key, skip test.
|
|
if ((authlim == 0) && (has_auth_key == false)) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "--- " _CYAN_("Known EV1/NTAG passwords"));
|
|
|
|
// test pwd gen A
|
|
num_to_bytes(ul_ev1_pwdgenA(card.uid), 4, key);
|
|
len = ulev1_requestAuthentication(key, pack, sizeof(pack));
|
|
if (len > -1) {
|
|
has_auth_key = true;
|
|
ak_len = 4;
|
|
memcpy(authenticationkey, key, 4);
|
|
PrintAndLogEx(SUCCESS, "Password... " _GREEN_("%s") " pack... " _GREEN_("%02X%02X"), sprint_hex_inrow(key, 4), pack[0], pack[1]);
|
|
goto out;
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// test pwd gen B
|
|
num_to_bytes(ul_ev1_pwdgenB(card.uid), 4, key);
|
|
len = ulev1_requestAuthentication(key, pack, sizeof(pack));
|
|
if (len > -1) {
|
|
has_auth_key = true;
|
|
ak_len = 4;
|
|
memcpy(authenticationkey, key, 4);
|
|
PrintAndLogEx(SUCCESS, "Password... " _GREEN_("%s") " pack... " _GREEN_("%02X%02X"), sprint_hex_inrow(key, 4), pack[0], pack[1]);
|
|
goto out;
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// test pwd gen C
|
|
num_to_bytes(ul_ev1_pwdgenC(card.uid), 4, key);
|
|
len = ulev1_requestAuthentication(key, pack, sizeof(pack));
|
|
if (len > -1) {
|
|
has_auth_key = true;
|
|
ak_len = 4;
|
|
memcpy(authenticationkey, key, 4);
|
|
PrintAndLogEx(SUCCESS, "Password... " _GREEN_("%s") " pack... " _GREEN_("%02X%02X"), sprint_hex_inrow(key, 4), pack[0], pack[1]);
|
|
goto out;
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// test pwd gen D
|
|
num_to_bytes(ul_ev1_pwdgenD(card.uid), 4, key);
|
|
len = ulev1_requestAuthentication(key, pack, sizeof(pack));
|
|
if (len > -1) {
|
|
has_auth_key = true;
|
|
ak_len = 4;
|
|
memcpy(authenticationkey, key, 4);
|
|
PrintAndLogEx(SUCCESS, "Password... " _GREEN_("%s") " pack... " _GREEN_("%02X%02X"), sprint_hex_inrow(key, 4), pack[0], pack[1]);
|
|
goto out;
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < ARRAYLEN(default_pwd_pack); ++i) {
|
|
key = default_pwd_pack[i];
|
|
len = ulev1_requestAuthentication(key, pack, sizeof(pack));
|
|
if (len > -1) {
|
|
has_auth_key = true;
|
|
ak_len = 4;
|
|
memcpy(authenticationkey, key, 4);
|
|
PrintAndLogEx(SUCCESS, "Password... " _GREEN_("%s") " pack... " _GREEN_("%02X%02X"), sprint_hex_inrow(key, 4), pack[0], pack[1]);
|
|
break;
|
|
} else {
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), false) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
}
|
|
if (len < 1) {
|
|
PrintAndLogEx(WARNING, _YELLOW_("password not known"));
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfu pwdgen -r`") " to get see known pwd gen algo suggestions");
|
|
}
|
|
} else {
|
|
if (locked) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfu pwdgen -r`") " to get see known pwd gen algo suggestions");
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
DropField();
|
|
|
|
mfu_fingerprint(tagtype, has_auth_key, auth_key_ptr, ak_len, use_schann);
|
|
|
|
if (locked) {
|
|
PrintAndLogEx(INFO, "\nTag appears to be locked, try using a key to get more info");
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfu pwdgen -r`") " to get see known pwd gen algo suggestions");
|
|
}
|
|
|
|
if (tagtype & (MFU_TT_MAGIC_1A | MFU_TT_MAGIC_1B | MFU_TT_MAGIC_2)) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`script run hf_mfu_setuid -h`") " to set UID");
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Write Single Block
|
|
//
|
|
static int CmdHF14AMfUWrBl(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu wrbl",
|
|
"Write a block. It autodetects card type.",
|
|
"hf mfu wrbl -b 0 -d 01234567\n"
|
|
"hf mfu wrbl -b 0 -d 01234567 -k AABBCCDD\n"
|
|
"hf mfu wrbl -b 0 -d 01234567 -k 00112233445566778899AABBCCDDEEFF\n"
|
|
"hf mfu wrbl -b 0 -d 01234567 -k 00112233445566778899AABBCCDDEEFF --schann"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_int1("b", "block", "<dec>", "Block number to write"),
|
|
arg_str1("d", "data", "<hex>", "Block data (4 or 16 hex bytes, 16 hex bytes will do a compatibility write)"),
|
|
arg_lit0(NULL, "force", "Force operation even if address is out of range"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
|
|
int blockno = arg_get_int_def(ctx, 3, -1);
|
|
|
|
int datalen = 0;
|
|
uint8_t data[16] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 4, data, &datalen);
|
|
|
|
bool force = arg_get_lit(ctx, 5);
|
|
bool use_schann = arg_get_lit(ctx, 6);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
if (ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (ak_len == 4) {
|
|
has_pwd = true;
|
|
} else if (ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (blockno < 0) {
|
|
PrintAndLogEx(WARNING, "Wrong block number");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (datalen != 16 && datalen != 4) {
|
|
PrintAndLogEx(WARNING, "Wrong data length. Expect 16 or 4, got %d", datalen);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
|
|
// starting with getting tagtype
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t maxblockno = 0;
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
maxblockno = UL_MEMORY_ARRAY[idx];
|
|
break;
|
|
}
|
|
}
|
|
if ((blockno > maxblockno) && (!force)) {
|
|
PrintAndLogEx(WARNING, "block number too large. Max block is %u/0x%02X \n", maxblockno, maxblockno);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// ONLY UL-C supports Compability Write, not UL-AES
|
|
if ((datalen == 16) && ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
PrintAndLogEx(WARNING, "UL-AES doesn't support 16 byte compability writes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
if (blockno <= 3)
|
|
PrintAndLogEx(INFO, "Special block: %0d (0x%02X) [ %s]", blockno, blockno, sprint_hex(data, datalen));
|
|
else
|
|
PrintAndLogEx(INFO, "Block: %0d (0x%02X) [ %s]", blockno, blockno, sprint_hex(data, datalen));
|
|
|
|
if (has_auth_key) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "3des", sprint_hex_inrow(authenticationkey, ak_len));
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "aes", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
} else if (has_pwd) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "pwd", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
keytype = 1; // UL_C auth
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
keytype = 3; // UL_AES auth
|
|
} else {
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
}
|
|
|
|
// Send write Block.
|
|
uint8_t *d = data;
|
|
int res = 0;
|
|
if (datalen == 16) {
|
|
// Comp write may take 16bytes, but only write 4bytes. See UL-C datasheet
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
|
|
res = mfu_write_block(d, 4, keytype, auth_key_ptr, blockno + i, use_schann);
|
|
if (res == PM3_SUCCESS) {
|
|
d += 4;
|
|
} else {
|
|
PrintAndLogEx(INFO, "Write ( %s )", _RED_("fail"));
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu rdbl -b %u") "` to verify ", blockno);
|
|
}
|
|
|
|
} else {
|
|
res = mfu_write_block(data, datalen, keytype, auth_key_ptr, blockno, use_schann);
|
|
switch (res) {
|
|
case PM3_SUCCESS: {
|
|
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu rdbl -b %u") "` to verify ", blockno);
|
|
break;
|
|
}
|
|
case PM3_ESOFT: {
|
|
PrintAndLogEx(FAILED, "Write ( " _RED_("fail") " )");
|
|
PrintAndLogEx(HINT, "Hint: Check password / key!");
|
|
break;
|
|
}
|
|
case PM3_ETIMEOUT:
|
|
default: {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
//
|
|
// Read Single Block
|
|
//
|
|
static int CmdHF14AMfURdBl(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu rdbl",
|
|
"Read a block and print. It autodetects card type.",
|
|
"hf mfu rdbl -b 0\n"
|
|
"hf mfu rdbl -b 0 -k AABBCCDD\n"
|
|
"hf mfu rdbl -b 0 --key 00112233445566778899AABBCCDDEEFF\n"
|
|
"hf mfu rdbl -b 0 --key 00112233445566778899AABBCCDDEEFF --schann"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_int1("b", "block", "<dec>", "Block number to read"),
|
|
arg_lit0(NULL, "force", "Force operation even if address is out of range"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
int blockno = arg_get_int_def(ctx, 3, -1);
|
|
bool force = arg_get_lit(ctx, 4);
|
|
bool use_schann = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
if (ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (ak_len == 4) {
|
|
has_pwd = true;
|
|
} else if (ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (blockno < 0) {
|
|
PrintAndLogEx(WARNING, "Wrong block number");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
|
|
// start with getting tagtype
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t maxblockno = 0;
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
maxblockno = UL_MEMORY_ARRAY[idx];
|
|
break;
|
|
}
|
|
}
|
|
if ((blockno > maxblockno) && (!force)) {
|
|
PrintAndLogEx(WARNING, "block number to large. Max block is %u/0x%02X \n", maxblockno, maxblockno);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
if (has_auth_key) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "3des", sprint_hex_inrow(authenticationkey, ak_len));
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "aes", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
} else if (has_pwd) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "pwd", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
|
|
|
|
// read block
|
|
mful_readblock_t packet = {
|
|
.block_no = blockno,
|
|
.use_schann = use_schann,
|
|
.num_of_blocks = 1,
|
|
};
|
|
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
packet.keytype = 1; // UL_C auth
|
|
packet.keylen = 16;
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
packet.keytype = 3; // UL_AES auth
|
|
packet.keylen = 16;
|
|
} else {
|
|
packet.keytype = 2; // UL_EV1/NTAG auth
|
|
packet.keylen = 4;
|
|
}
|
|
}
|
|
|
|
memcpy(packet.key, auth_key_ptr, packet.keylen);
|
|
|
|
PrintAndLogEx(INFO, "using secure channel... %s", (use_schann) ? _GREEN_("yes") : _YELLOW_("no"));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READBL, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_READBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Failed reading block %u", blockno);
|
|
return resp.status;
|
|
}
|
|
|
|
uint8_t *d = resp.data.asBytes;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Block# | Data | Ascii");
|
|
PrintAndLogEx(INFO, "-----------------------------");
|
|
PrintAndLogEx(INFO, "%02d/0x%02X | %s| %s\n", blockno, blockno, sprint_hex(d, 4), sprint_ascii(d, 4));
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
void mfu_print_dump(mfu_dump_t *card, uint16_t pages, uint8_t startpage, bool dense_output) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, _CYAN_("MFU dump file information"));
|
|
PrintAndLogEx(INFO, "-------------------------------------------------------------");
|
|
PrintAndLogEx(INFO, "Version..... " _YELLOW_("%s"), sprint_hex(card->version, sizeof(card->version)));
|
|
PrintAndLogEx(INFO, "TBD 0....... %s", sprint_hex(card->tbo, sizeof(card->tbo)));
|
|
PrintAndLogEx(INFO, "TBD 1....... %s", sprint_hex(card->tbo1, sizeof(card->tbo1)));
|
|
PrintAndLogEx(INFO, "Signature... %s", sprint_hex(card->signature, 16));
|
|
PrintAndLogEx(INFO, " %s", sprint_hex(card->signature + 16, sizeof(card->signature) - 16));
|
|
for (uint8_t i = 0; i < 3; i ++) {
|
|
PrintAndLogEx(INFO, "Counter %d... %s", i, sprint_hex(card->counter_tearing[i], 3));
|
|
PrintAndLogEx(INFO, "Tearing %d... %s", i, sprint_hex(card->counter_tearing[i] + 3, 1));
|
|
}
|
|
|
|
// 0-bases index, to get total bytes, its +1 page.
|
|
// UL-C,
|
|
// Max index page is 47.
|
|
// total pages is 48
|
|
// total bytes is 192
|
|
PrintAndLogEx(INFO, "Max data page... " _YELLOW_("%d") " ( " _YELLOW_("%d") " bytes )", card->pages, (card->pages + 1) * MFU_BLOCK_SIZE);
|
|
PrintAndLogEx(INFO, "Header size..... %d bytes", MFU_DUMP_PREFIX_LENGTH);
|
|
|
|
uint8_t j = 0;
|
|
bool lckbit = false;
|
|
uint8_t *data = card->data;
|
|
|
|
uint8_t lockbytes_sta[] = {0, 0};
|
|
uint8_t lockbytes_dyn[] = {0, 0, 0};
|
|
bool bit_stat[16] = {0};
|
|
bool bit_dyn[16] = {0};
|
|
|
|
if (startpage == 0) {
|
|
// Load static lock bytes.
|
|
memcpy(lockbytes_sta, data + 10, sizeof(lockbytes_sta));
|
|
for (j = 0; j < 16; j++) {
|
|
bit_stat[j] = lockbytes_sta[j / 8] & (1 << (7 - j % 8));
|
|
}
|
|
}
|
|
|
|
// Load dynamic lockbytes if available
|
|
// TODO -- FIGURE OUT LOCK BYTES FOR TO EV1 and/or NTAG
|
|
if ((startpage == 0) && (pages == 44)) {
|
|
|
|
memcpy(lockbytes_dyn, data + (40 * 4), sizeof(lockbytes_dyn));
|
|
|
|
for (j = 0; j < 16; j++) {
|
|
bit_dyn[j] = lockbytes_dyn[j / 8] & (1 << (7 - j % 8));
|
|
}
|
|
PrintAndLogEx(INFO, "Dynamic lock.... %s", sprint_hex(lockbytes_dyn, 3));
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "-------------------------------------------------------------");
|
|
PrintAndLogEx(INFO, "block# | data |lck| ascii");
|
|
PrintAndLogEx(INFO, "---------+-------------+---+------");
|
|
|
|
bool in_repeated_block = false;
|
|
|
|
for (uint16_t i = 0; i < pages; ++i) {
|
|
if (i + startpage < 3) {
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | " _RED_("%s")"| | %s",
|
|
i + startpage,
|
|
i + startpage,
|
|
sprint_hex(data + i * 4, 4),
|
|
sprint_ascii(data + i * 4, 4)
|
|
);
|
|
continue;
|
|
}
|
|
switch (i) {
|
|
case 3:
|
|
lckbit = bit_stat[4];
|
|
break;
|
|
case 4:
|
|
lckbit = bit_stat[3];
|
|
break;
|
|
case 5:
|
|
lckbit = bit_stat[2];
|
|
break;
|
|
case 6:
|
|
lckbit = bit_stat[1];
|
|
break;
|
|
case 7:
|
|
lckbit = bit_stat[0];
|
|
break;
|
|
case 8:
|
|
lckbit = bit_stat[15];
|
|
break;
|
|
case 9:
|
|
lckbit = bit_stat[14];
|
|
break;
|
|
case 10:
|
|
lckbit = bit_stat[13];
|
|
break;
|
|
case 11:
|
|
lckbit = bit_stat[12];
|
|
break;
|
|
case 12:
|
|
lckbit = bit_stat[11];
|
|
break;
|
|
case 13:
|
|
lckbit = bit_stat[10];
|
|
break;
|
|
case 14:
|
|
lckbit = bit_stat[9];
|
|
break;
|
|
case 15:
|
|
lckbit = bit_stat[8];
|
|
break;
|
|
case 16:
|
|
case 17:
|
|
case 18:
|
|
case 19:
|
|
lckbit = bit_dyn[6];
|
|
break;
|
|
case 20:
|
|
case 21:
|
|
case 22:
|
|
case 23:
|
|
lckbit = bit_dyn[5];
|
|
break;
|
|
case 24:
|
|
case 25:
|
|
case 26:
|
|
case 27:
|
|
lckbit = bit_dyn[4];
|
|
break;
|
|
case 28:
|
|
case 29:
|
|
case 30:
|
|
case 31:
|
|
lckbit = bit_dyn[2];
|
|
break;
|
|
case 32:
|
|
case 33:
|
|
case 34:
|
|
case 35:
|
|
lckbit = bit_dyn[1];
|
|
break;
|
|
case 36:
|
|
case 37:
|
|
case 38:
|
|
case 39:
|
|
lckbit = bit_dyn[0];
|
|
break;
|
|
case 40:
|
|
lckbit = bit_dyn[12];
|
|
break;
|
|
case 41:
|
|
lckbit = bit_dyn[11];
|
|
break;
|
|
case 42:
|
|
lckbit = bit_dyn[10];
|
|
break; //auth0
|
|
case 43:
|
|
lckbit = bit_dyn[9];
|
|
break; //auth1
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
// suppress repeating blocks, truncate as such that the first and last block with the same data is shown
|
|
// but the blocks in between are replaced with a single line of "......" if dense_output is enabled
|
|
const uint8_t *blk = data + (i * MFU_BLOCK_SIZE);
|
|
if (dense_output &&
|
|
(i > 3) &&
|
|
(i < pages) &&
|
|
(in_repeated_block == false) &&
|
|
(memcmp(blk, blk - MFU_BLOCK_SIZE, MFU_BLOCK_SIZE) == 0) &&
|
|
(memcmp(blk, blk + MFU_BLOCK_SIZE, MFU_BLOCK_SIZE) == 0) &&
|
|
(memcmp(blk, blk + (MFU_BLOCK_SIZE * 2), MFU_BLOCK_SIZE) == 0)
|
|
) {
|
|
// we're in a user block that isn't the first user block nor last two user blocks,
|
|
// and the current block data is the same as the previous and next two block
|
|
in_repeated_block = true;
|
|
PrintAndLogEx(INFO, " ......");
|
|
|
|
} else if (in_repeated_block &&
|
|
(memcmp(blk, blk + MFU_BLOCK_SIZE, MFU_BLOCK_SIZE) || i == pages)
|
|
) {
|
|
// in a repeating block, but the next block doesn't match anymore, or we're at the end block
|
|
in_repeated_block = false;
|
|
}
|
|
|
|
const char *lckbitchar = "?";
|
|
if ((startpage == 0) && ((i < 16) || (pages == 44))) {
|
|
lckbitchar = (lckbit) ? _RED_("1") : "0";
|
|
}
|
|
|
|
if (in_repeated_block == false) {
|
|
|
|
if (i == 3) {
|
|
// otp block
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s")"| %s | %s"
|
|
, i + startpage
|
|
, i + startpage
|
|
, sprint_hex(data + i * 4, 4)
|
|
, lckbitchar
|
|
, sprint_ascii(data + i * 4, 4)
|
|
);
|
|
|
|
} else {
|
|
// normal block
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | %s| %s | %s"
|
|
, i + startpage
|
|
, i + startpage
|
|
, sprint_hex(data + i * 4, 4)
|
|
, lckbitchar
|
|
, sprint_ascii(data + i * 4, 4)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
PrintAndLogEx(INFO, "---------------------------------");
|
|
}
|
|
|
|
//
|
|
// Mifare Ultralight / Ultralight-C / Ultralight-EV1
|
|
// Read and Dump Card Contents, using auto detection of tag size.
|
|
static int CmdHF14AMfUDump(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu dump",
|
|
"Dump MIFARE Ultralight/NTAG tag to files (bin/json)\n"
|
|
"It autodetects card type."
|
|
"Supports:\n"
|
|
"Ultralight, Ultralight C, Ultralight AES, Ultralight EV1\n"
|
|
"NTAG 203, NTAG 210, NTAG 212, NTAG 213, NTAG 215, NTAG 216\n",
|
|
"hf mfu dump -f myfile\n"
|
|
"hf mfu dump -k AABBCCDD -> dump whole tag using pwd AABBCCDD\n"
|
|
"hf mfu dump -p 10 -> start at page 10 and dump rest of blocks\n"
|
|
"hf mfu dump -p 10 -q 2 -> start at page 10 and dump two blocks\n"
|
|
"hf mfu dump --key 00112233445566778899AABBCCDDEEFF\n"
|
|
"\n"
|
|
"Note: Dumping a NTAG/UL tag to a UMC will likely result in incorrect PWD and PACK\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_str0("k", "key", "<hex>", "Key for authentication (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_int0("p", "page", "<dec>", "Manually set start page number to start from"),
|
|
arg_int0("q", "qty", "<dec>", "Manually set number of pages to dump"),
|
|
arg_lit0(NULL, "ns", "no save to file"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 2, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 3);
|
|
int start_page = arg_get_int_def(ctx, 4, 0);
|
|
int pages = arg_get_int_def(ctx, 5, 16);
|
|
bool nosave = arg_get_lit(ctx, 6);
|
|
bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 7));
|
|
bool use_schann = arg_get_lit(ctx, 8);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
if (ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (ak_len == 4) {
|
|
has_pwd = true;
|
|
} else if (ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool manual_pages = false;
|
|
if (start_page > 0) {
|
|
manual_pages = true;
|
|
}
|
|
|
|
if (pages != 16) {
|
|
manual_pages = true;
|
|
}
|
|
|
|
uint8_t card_mem_size = 0;
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
//get number of pages to read
|
|
if (manual_pages == false) {
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
//add one as maxblks starts at 0
|
|
card_mem_size = pages = UL_MEMORY_ARRAY[idx] + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ul_print_type(tagtype, 0);
|
|
PrintAndLogEx(SUCCESS, "Reading tag memory...");
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)
|
|
keytype = 1; // UL_C auth
|
|
else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)
|
|
keytype = 3; // UL_AES auth
|
|
else
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// read card
|
|
mful_readblock_t packet = {
|
|
.block_no = start_page,
|
|
.num_of_blocks = pages,
|
|
.keytype = keytype,
|
|
.keylen = ak_len,
|
|
.use_schann = use_schann,
|
|
};
|
|
memcpy(packet.key, auth_key_ptr, ak_len);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READCARD, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_READCARD, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Failed dumping card");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
setDeviceDebugLevel(dbg_curr, false);
|
|
|
|
// read all memory
|
|
uint8_t data[1024] = {0x00};
|
|
memset(data, 0x00, sizeof(data));
|
|
|
|
mful_readblock_resp_t *payload = (mful_readblock_resp_t *)resp.data.asBytes;
|
|
|
|
uint32_t startindex = payload->startidx;
|
|
|
|
uint32_t buffer_size = payload->bytelen;
|
|
if (buffer_size > sizeof(data)) {
|
|
PrintAndLogEx(FAILED, "Data exceeded buffer size!");
|
|
buffer_size = sizeof(data);
|
|
}
|
|
|
|
if (GetFromDevice(BIG_BUF, data, buffer_size, startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
bool is_partial = (pages != buffer_size / MFU_BLOCK_SIZE);
|
|
|
|
pages = buffer_size / MFU_BLOCK_SIZE;
|
|
|
|
if (is_partial) {
|
|
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) || ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
if (card_mem_size != (pages + 4)) {
|
|
PrintAndLogEx(INFO, "Partial dump, got " _RED_("%d") " bytes - card mem size is %u bytes", pages * MFU_BLOCK_SIZE, card_mem_size * MFU_BLOCK_SIZE);
|
|
PrintAndLogEx(HINT, "Hint: Try using a key");
|
|
}
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Try using a password");
|
|
}
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
mfu_dump_t dump_file_data;
|
|
memset(&dump_file_data, 0, sizeof(dump_file_data));
|
|
uint8_t get_version[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
uint8_t get_counter_tearing[][4] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}};
|
|
uint8_t get_signature[32];
|
|
memset(get_signature, 0, sizeof(get_signature));
|
|
|
|
// not ul_c and not std ul then attempt to collect info like
|
|
// VERSION, SIGNATURE, COUNTERS, TEARING, PACK,
|
|
if (!(tagtype & MFU_TT_UL_C || tagtype & MFU_TT_UL || tagtype & MFU_TT_MY_D_MOVE || tagtype & MFU_TT_MY_D_MOVE_LEAN)) {
|
|
// attempt to read pack
|
|
bool has_key = (has_auth_key || has_pwd);
|
|
uint8_t get_pack[] = {0, 0};
|
|
if (ul_auth_select(&card, tagtype, has_key, auth_key_ptr, get_pack, sizeof(get_pack), false) != PM3_SUCCESS) {
|
|
//reset pack
|
|
get_pack[0] = 0;
|
|
get_pack[1] = 0;
|
|
}
|
|
DropField();
|
|
|
|
// only add pack if not partial read, and complete pages read.
|
|
if (!is_partial && pages == card_mem_size) {
|
|
|
|
// add pack to block read
|
|
memcpy(data + (pages * 4) - 4, get_pack, sizeof(get_pack));
|
|
}
|
|
|
|
if (has_auth_key) {
|
|
uint8_t dummy_pack[] = {0, 0};
|
|
ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, dummy_pack, sizeof(dummy_pack), use_schann);
|
|
} else {
|
|
ul_select(&card);
|
|
}
|
|
|
|
ulev1_getVersion(get_version, sizeof(get_version), use_schann);
|
|
|
|
// ULEV-1 has 3 counters
|
|
uint8_t n = 0;
|
|
|
|
// NTAG has 1 counter, at 0x02
|
|
if ((tagtype & (MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C | MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216))) {
|
|
n = 2;
|
|
}
|
|
|
|
// NTAG can have nfc counter pwd protection enabled
|
|
for (; n < 3; n++) {
|
|
|
|
if (has_auth_key) {
|
|
uint8_t dummy_pack[] = {0, 0};
|
|
ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, dummy_pack, sizeof(dummy_pack), use_schann);
|
|
} else {
|
|
ul_select(&card);
|
|
}
|
|
ulev1_readCounter(n, &get_counter_tearing[n][0], 3, use_schann);
|
|
|
|
if (has_auth_key) {
|
|
uint8_t dummy_pack[] = {0, 0};
|
|
ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, dummy_pack, sizeof(dummy_pack), false);
|
|
} else {
|
|
ul_select(&card);
|
|
}
|
|
ulev1_readTearing(n, &get_counter_tearing[n][3], 1);
|
|
}
|
|
|
|
DropField();
|
|
|
|
if (has_auth_key) {
|
|
uint8_t dummy_pack[] = {0, 0};
|
|
ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, dummy_pack, sizeof(dummy_pack), use_schann);
|
|
} else {
|
|
ul_select(&card);
|
|
}
|
|
|
|
ulev1_readSignature(get_signature, sizeof(get_signature), use_schann);
|
|
DropField();
|
|
}
|
|
|
|
|
|
// format and add keys to block dump output
|
|
// only add keys if not partial read, and complete pages read
|
|
|
|
// UL-C/UL-AES add a working known key
|
|
if (has_auth_key && ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C || (tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) { // add 4 pages of key
|
|
|
|
// if we didn't swapendian before - do it now for the sprint_hex call
|
|
// NOTE: default entry is bigendian (unless swapped), sprint_hex outputs little endian
|
|
// need to swap to keep it the same
|
|
auth_key_ptr = authenticationkey;
|
|
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
if (swap_endian == false) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
}
|
|
memcpy(data + pages * MFU_BLOCK_SIZE, auth_key_ptr, ak_len);
|
|
pages += ak_len / MFU_BLOCK_SIZE;
|
|
}
|
|
if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) && (pages >= 0x2F)) {
|
|
if (swap_endian == false) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
memcpy(data + 0x30 * MFU_BLOCK_SIZE, auth_key_ptr, ak_len);
|
|
if (pages < 0x34) {
|
|
pages = 0x34;
|
|
}
|
|
}
|
|
|
|
// fix
|
|
if (is_partial && pages == card_mem_size) {
|
|
is_partial = false;
|
|
}
|
|
}
|
|
|
|
if (!is_partial && pages == card_mem_size && has_pwd) {
|
|
// if we didn't swapendian before - do it now for the sprint_hex call
|
|
// NOTE: default entry is bigendian (unless swapped), sprint_hex outputs little endian
|
|
// need to swap to keep it the same
|
|
if (swap_endian == false) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
} else {
|
|
auth_key_ptr = authenticationkey;
|
|
}
|
|
|
|
memcpy(data + (pages * MFU_BLOCK_SIZE) - 8, authenticationkey, ak_len);
|
|
}
|
|
|
|
//add *special* blocks to dump
|
|
// pack and pwd saved into last pages of dump, if was not partial read
|
|
dump_file_data.pages = pages - 1;
|
|
memcpy(dump_file_data.version, get_version, sizeof(dump_file_data.version));
|
|
memcpy(dump_file_data.signature, get_signature, sizeof(dump_file_data.signature));
|
|
memcpy(dump_file_data.counter_tearing, get_counter_tearing, sizeof(dump_file_data.counter_tearing));
|
|
memcpy(dump_file_data.data, data, pages * MFU_BLOCK_SIZE);
|
|
|
|
mfu_print_dump(&dump_file_data, pages, start_page, dense_output);
|
|
|
|
if (ndef_detect_message(dump_file_data.data, pages * MFU_BLOCK_SIZE)) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread")"`");
|
|
}
|
|
|
|
if (nosave) {
|
|
PrintAndLogEx(INFO, "Called with no save option");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// user supplied filename?
|
|
if (fnlen < 1) {
|
|
PrintAndLogEx(INFO, "Using UID as filename");
|
|
uint8_t uid[7] = {0};
|
|
memcpy(uid, (uint8_t *)&dump_file_data.data, 3);
|
|
memcpy(uid + 3, (uint8_t *)&dump_file_data.data + 4, 4);
|
|
strcat(filename, "hf-mfu-");
|
|
FillFileNameByUID(filename, uid, "-dump", sizeof(uid));
|
|
}
|
|
|
|
uint16_t datalen = MFU_DUMP_PREFIX_LENGTH + (pages * MFU_BLOCK_SIZE);
|
|
pm3_save_dump(filename, (uint8_t *)&dump_file_data, datalen, jsfMfuMemory);
|
|
|
|
if (is_partial) {
|
|
PrintAndLogEx(WARNING, "Partial dump created. (%d of %d blocks)", pages, card_mem_size);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void wait4response(uint32_t cmd, uint8_t b) {
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(cmd, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "failed to write block " _YELLOW_("%d"), b);
|
|
}
|
|
}
|
|
|
|
//
|
|
//Configure tamper feature of NTAG 213TT
|
|
//
|
|
int CmdHF14MfUTamper(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu tamper",
|
|
"Set the configuration of the NTAG 213TT tamper feature\n"
|
|
"Supports:\n"
|
|
"NTAG 213TT\n",
|
|
"hf mfu tamper -e -> enable tamper feature\n"
|
|
"hf mfu tamper -d -> disable tamper feature\n"
|
|
"hf mfu tamper -m 0A0A0A0A -> set the tamper message to 0A0A0A0A\n"
|
|
"hf mfu tamper --lockmessage -> permanently lock the tamper message and mask it from memory\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("e", "enable", "Enable the tamper feature"),
|
|
arg_lit0("d", "disable", "Disable the tamper feature"),
|
|
arg_str0("m", "message", "<hex>", "Set the tamper message (4 bytes)"),
|
|
arg_lit0(NULL, "lockmessage", "Permanently lock the tamper message and mask it from memory (does not lock tamper feature itself)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int msg_len = 0;
|
|
uint8_t msg_data[4] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 3, msg_data, &msg_len);
|
|
bool use_msg = (msg_len > 0);
|
|
|
|
if (use_msg && msg_len != 4) {
|
|
PrintAndLogEx(WARNING, "The tamper message must be 4 hex bytes if provided");
|
|
CLIParserFree(ctx);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
bool lock_msg = arg_get_lit(ctx, 4);
|
|
bool enable = arg_get_lit(ctx, 1);
|
|
bool disable = arg_get_lit(ctx, 2);
|
|
CLIParserFree(ctx);
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
DropField();
|
|
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
PrintAndLogEx(WARNING, "Tag type not detected");
|
|
return PM3_ESOFT;
|
|
}
|
|
if (tagtype != MFU_TT_NTAG_213_TT) {
|
|
PrintAndLogEx(WARNING, "Tag type not NTAG 213TT");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (enable && disable) {
|
|
PrintAndLogEx(WARNING, "You can only select one of the options enable/disable tamper feature");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
mful_writeblock_t packet = {
|
|
.keytype = 0, // no key
|
|
.use_schann = false,
|
|
.keylen = 0,
|
|
};
|
|
memcpy(packet.data, msg_data, msg_len);
|
|
|
|
if (use_msg) {
|
|
|
|
PrintAndLogEx(INFO, "Trying to write tamper message...");
|
|
|
|
int tt_msg_page = 45;
|
|
packet.block_no = tt_msg_page;
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Writing tamper message ( %s )", _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(FAILED, "Writing tamper message ( %s )", _RED_("fail"));
|
|
}
|
|
}
|
|
|
|
if (enable || disable || lock_msg) {
|
|
|
|
PrintAndLogEx(INFO, "Reading current tag config...");
|
|
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(ERR, "Unable to select tag");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
int tt_cfg_page = 41;
|
|
uint8_t cfg_page[4] = { 0x00 };
|
|
uint8_t cmd[] = { ISO14443A_CMD_READBLOCK, tt_cfg_page };
|
|
int status = ul_send_cmd_raw(cmd, sizeof(cmd), cfg_page, 4, false);
|
|
DropField();
|
|
|
|
if (status <= 0) {
|
|
PrintAndLogEx(WARNING, "Problem reading current config from tag");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (enable) {
|
|
cfg_page[1] |= 0x02;
|
|
PrintAndLogEx(INFO, "Enabling tamper feature");
|
|
}
|
|
|
|
if (disable) {
|
|
cfg_page[1] &= 0xFD;
|
|
PrintAndLogEx(INFO, "Disabling tamper feature");
|
|
}
|
|
|
|
if (lock_msg) {
|
|
cfg_page[1] |= 0x04;
|
|
PrintAndLogEx(INFO, "Locking tamper message");
|
|
}
|
|
|
|
packet.block_no = tt_cfg_page;
|
|
memcpy(packet.data, cfg_page, sizeof(cfg_page));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packet, sizeof(packet));
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Writing tamper configuration ( %s )", _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(FAILED, "Writing tamper configuration ( %s )", _RED_("fail"));
|
|
}
|
|
return resp.status;
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Restore dump file onto tag
|
|
//
|
|
static int CmdHF14AMfURestore(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu restore",
|
|
"Restore MIFARE Ultralight/NTAG dump file (bin/eml/json) to tag.\n",
|
|
"hf mfu restore -f myfile -s -> special write\n"
|
|
"hf mfu restore -f myfile -k AABBCCDD -s -> special write, use key\n"
|
|
"hf mfu restore -f myfile -k AABBCCDD -ser -> special write, use key, write dump pwd, ...\n"
|
|
"\n"
|
|
"Note: Restoring a NTAG/UL dump to a UMC will likely result in incorrect PWD and PACK\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_str0("k", "key", "<hex>", "key for authentication (UL-C 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "swap entered key's endianness"),
|
|
arg_lit0("s", NULL, "enable special write UID -MAGIC TAG ONLY-"),
|
|
arg_lit0("e", NULL, "enable special write version/signature -MAGIC NTAG 21* ONLY-"),
|
|
arg_lit0("r", NULL, "use password found in dumpfile to configure tag. Requires " _YELLOW_("'-e'") " parameter to work"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 2, authenticationkey, &ak_len);
|
|
|
|
bool swap_endian = arg_get_lit(ctx, 3);
|
|
bool write_special = arg_get_lit(ctx, 4);
|
|
bool write_extra = arg_get_lit(ctx, 5);
|
|
bool read_key = arg_get_lit(ctx, 6);
|
|
bool verbose = arg_get_lit(ctx, 7);
|
|
bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 8));
|
|
bool use_schann = arg_get_lit(ctx, 9);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_key = false;
|
|
if (ak_len > 0) {
|
|
if (ak_len != 4 && ak_len != 16) {
|
|
PrintAndLogEx(ERR, "Wrong key length. expected 4 or 16, got %d", ak_len);
|
|
return PM3_EINVARG;
|
|
} else {
|
|
has_key = true;
|
|
}
|
|
}
|
|
|
|
if (use_schann && has_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (fnlen == 0) {
|
|
char *fptr = mfu_generate_filename("hf-mfu-", "-dump.bin");
|
|
if (fptr != NULL) {
|
|
strncpy(filename, fptr, sizeof(filename) - 1);
|
|
} else {
|
|
snprintf(filename, sizeof(filename), "dumpdata.bin");
|
|
}
|
|
free(fptr);
|
|
}
|
|
|
|
// read dump file
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 0;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (MFU_MAX_BYTES + MFU_DUMP_PREFIX_LENGTH));
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (bytes_read < MFU_DUMP_PREFIX_LENGTH) {
|
|
PrintAndLogEx(ERR, "Error, dump file is too small");
|
|
free(dump);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = convert_mfu_dump_format(&dump, &bytes_read, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Failed convert on load to new Ultralight/NTAG format");
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
mfu_dump_t *mem = (mfu_dump_t *)dump;
|
|
uint8_t pages = (bytes_read - MFU_DUMP_PREFIX_LENGTH) / MFU_BLOCK_SIZE;
|
|
|
|
if (pages - 1 != mem->pages) {
|
|
PrintAndLogEx(ERR, "Error, invalid dump, wrong page count");
|
|
PrintAndLogEx(INFO, " %u vs mempg %u", pages - 1, mem->pages);
|
|
free(dump);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
free(dump);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
if ((has_key == true) && (ak_len != 16)) {
|
|
PrintAndLogEx(ERR, "UL-C key must be 16 bytes");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (write_extra == true) {
|
|
PrintAndLogEx(ERR, "Option -e incompatible with your UL-C card");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
if ((has_key == true) && (ak_len != 16)) {
|
|
PrintAndLogEx(ERR, "UL-AES key must be 16 bytes");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (write_extra == true) {
|
|
PrintAndLogEx(ERR, "Option -e incompatible with your UL-AEScard");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
if ((has_key == true) && (ak_len == 16)) {
|
|
PrintAndLogEx(ERR, "UL PWD must be 4 bytes");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
PrintAndLogEx(ERR, "Sorry, UL-AES not yet supported. Feel free to implement!");
|
|
free(dump);
|
|
return PM3_ENOTIMPL;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Restoring " _YELLOW_("%s")" to card", filename);
|
|
|
|
mfu_print_dump(mem, pages, 0, dense_output);
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
mful_writeblock_t packetw = {
|
|
.keytype = 0,
|
|
.keylen = 0,
|
|
.use_schann = use_schann,
|
|
};
|
|
if (has_key) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
packetw.keytype = 1; // UL_C auth
|
|
packetw.keylen = 16;
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
packetw.keytype = 3; // UL_AES auth
|
|
packetw.keylen = 16;
|
|
} else {
|
|
packetw.keytype = 2; // UL_EV1/NTAG auth
|
|
packetw.keylen = 4;
|
|
}
|
|
memcpy(packetw.key, auth_key_ptr, ak_len);
|
|
}
|
|
|
|
// write version, signature, pack
|
|
// only magic NTAG cards
|
|
if (write_extra) {
|
|
|
|
#define MFU_NTAG_SPECIAL_PWD 0xF0
|
|
#define MFU_NTAG_SPECIAL_PACK 0xF1
|
|
#define MFU_NTAG_SPECIAL_VERSION 0xFA
|
|
#define MFU_NTAG_SPECIAL_SIGNATURE 0xF2
|
|
// pwd
|
|
if (has_key || read_key) {
|
|
|
|
memcpy(packetw.data, auth_key_ptr, 4);
|
|
if (read_key) {
|
|
// try reading key from dump and use.
|
|
memcpy(packetw.data, mem->data + (bytes_read - MFU_DUMP_PREFIX_LENGTH - 8), 4);
|
|
}
|
|
packetw.block_no = MFU_NTAG_SPECIAL_PWD;
|
|
|
|
PrintAndLogEx(INFO, "special PWD block written 0x%X - %s", MFU_NTAG_SPECIAL_PWD, sprint_hex(packetw.data, 4));
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, MFU_NTAG_SPECIAL_PWD);
|
|
|
|
// copy the new key
|
|
packetw.keytype = 2;
|
|
packetw.keylen = 4;
|
|
memcpy(packetw.key, packetw.data, 4);
|
|
}
|
|
|
|
// pack
|
|
memcpy(packetw.data, mem->data + (bytes_read - MFU_DUMP_PREFIX_LENGTH - 4), 2);
|
|
packetw.data[2] = 0;
|
|
packetw.data[3] = 0;
|
|
packetw.block_no = MFU_NTAG_SPECIAL_PACK;
|
|
PrintAndLogEx(INFO, "special PACK block written 0x%X - %s", MFU_NTAG_SPECIAL_PACK, sprint_hex(packetw.data, 4));
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, MFU_NTAG_SPECIAL_PACK);
|
|
|
|
// Signature
|
|
for (uint8_t s = MFU_NTAG_SPECIAL_SIGNATURE, i = 0; s < MFU_NTAG_SPECIAL_SIGNATURE + 8; s++, i += 4) {
|
|
memcpy(packetw.data, mem->signature + i, 4);
|
|
packetw.block_no = s;
|
|
PrintAndLogEx(INFO, "special SIG block written 0x%X - %s", s, sprint_hex(packetw.data, 4));
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, s);
|
|
}
|
|
|
|
// Version
|
|
for (uint8_t s = MFU_NTAG_SPECIAL_VERSION, i = 0; s < MFU_NTAG_SPECIAL_VERSION + 2; s++, i += 4) {
|
|
memcpy(packetw.data, mem->version + i, 4);
|
|
packetw.block_no = s;
|
|
PrintAndLogEx(INFO, "special VERSION block written 0x%X - %s", s, sprint_hex(packetw.data, 4));
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, s);
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Restoring data blocks.");
|
|
PrintAndLogEx(INFO, "." NOLF);
|
|
// write all other data
|
|
// Skip block 0,1,2,3 (only magic tags can write to them)
|
|
// Skip last 5 blocks usually is configuration
|
|
for (uint8_t b = 4; b < pages - 5; b++) {
|
|
|
|
//Send write Block
|
|
memcpy(packetw.data, mem->data + (b * 4), 4);
|
|
packetw.block_no = b;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, b);
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
fflush(stdout);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// write special data last
|
|
if (write_special) {
|
|
|
|
PrintAndLogEx(INFO, "Restoring configuration blocks");
|
|
|
|
PrintAndLogEx(INFO, "Authentication with keytype[%i] = %s\n", packetw.keytype, sprint_hex(packetw.key, packetw.keylen));
|
|
|
|
#if defined ICOPYX
|
|
// otp, uid, lock, dynlockbits, cfg0, cfg1, pwd, pack
|
|
uint8_t blocks[] = {3, 0, 1, 2, pages - 5, pages - 4, pages - 3, pages - 2, pages - 1};
|
|
#else
|
|
// otp, uid, lock, dynlockbits, cfg0, cfg1
|
|
uint8_t blocks[] = {3, 0, 1, 2, pages - 5, pages - 4, pages - 3};
|
|
#endif
|
|
for (uint8_t i = 0; i < ARRAYLEN(blocks); i++) {
|
|
uint8_t b = blocks[i];
|
|
memcpy(packetw.data, mem->data + (b * 4), 4);
|
|
packetw.block_no = b;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
wait4response(CMD_HF_MIFAREU_WRITEBL, b);
|
|
PrintAndLogEx(INFO, "special block written " _YELLOW_("%u") " - %s", b, sprint_hex(packetw.data, 4));
|
|
}
|
|
}
|
|
|
|
DropField();
|
|
free(dump);
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu dump --ns") "` to verify");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
return PM3_SUCCESS;
|
|
}
|
|
//
|
|
// Load emulator with dump file
|
|
//
|
|
static int CmdHF14AMfUeLoad(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu eload",
|
|
"Load emulator memory with data from (bin/eml/json) dump file\n",
|
|
"hf mfu eload -f hf-mfu-04010203040506.bin\n"
|
|
"hf mfu eload -f hf-mfu-04010203040506.bin -q 57 -> load 57 blocks from myfile"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_int0("q", "qty", "<dec>", "Number of blocks to load from eml file"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
CLIParserFree(ctx);
|
|
|
|
size_t nc_len = strlen(Cmd) + 6;
|
|
char *nc = calloc(nc_len, 1);
|
|
if (nc == NULL) {
|
|
return CmdHF14AMfELoad(Cmd);
|
|
}
|
|
|
|
snprintf(nc, nc_len, "%s --ul", Cmd);
|
|
int res = CmdHF14AMfELoad(nc);
|
|
free(nc);
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfu sim -t 7`") " to simulate an Amiibo.");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// Simulate tag
|
|
//
|
|
static int CmdHF14AMfUSim(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu sim",
|
|
"Simulate MIFARE Ultralight family type based upon\n"
|
|
"ISO/IEC 14443 type A tag with 4,7 or 10 byte UID\n"
|
|
"from emulator memory. See `hf mfu eload` first. \n"
|
|
"The UID from emulator memory will be used if not specified.\n"
|
|
"See `hf 14a sim -h` to see available types. You want 2, 7, 13 or 14 usually.",
|
|
"hf mfu sim -t 2 --uid 11223344556677 -> MIFARE Ultralight\n"
|
|
"hf mfu sim -t 7 --uid 11223344556677 -n 5 -> MFU EV1 / NTAG 215 Amiibo\n"
|
|
"hf mfu sim -t 7 -> MFU EV1 / NTAG 215 Amiibo\n"
|
|
"hf mfu sim -t 13 -> MIFARE Ultralight C\n"
|
|
"hf mfu sim -t 14 -> MIFARE Ultralight AES\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("t", "type", "<1..14> ", "Simulation type to use"),
|
|
arg_str0("u", "uid", "<hex>", "<4|7|10> hex bytes UID"),
|
|
arg_int0("n", "num", "<dec>", "Exit simulation after <numreads> blocks. 0 = infinite"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_str0(NULL, "1a1", "<hex>", "<8|16> hex bytes ULC/ULAES Auth reply step1: ek(RndB)"),
|
|
arg_str0(NULL, "1a2", "<hex>", "<8|16> hex bytes ULC/ULAES Auth reply step2: ek(RndA')"),
|
|
arg_lit0(NULL, "1a2-mirror", "Mirror ek(RndA) from step1 reply into step2 reply"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
CLIParserFree(ctx);
|
|
return CmdHF14ASim(Cmd);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
// Ultralight C & AES helpers
|
|
//-------------------------------------------------------------------------------
|
|
|
|
static int mfu_3pass_load_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, const char *filename, int fnlen, uint8_t keysize) {
|
|
// Handle Keys
|
|
*pkeycnt = 0;
|
|
*pkeyBlock = NULL;
|
|
uint8_t *p;
|
|
// Handle user supplied dictionary file
|
|
if (fnlen > 0) {
|
|
uint32_t loaded_numKeys = 0;
|
|
uint8_t *keyBlock_tmp = NULL;
|
|
int res = loadFileDICTIONARY_safe(filename, (void **) &keyBlock_tmp, keysize, &loaded_numKeys);
|
|
if (res != PM3_SUCCESS || loaded_numKeys == 0 || keyBlock_tmp == NULL) {
|
|
PrintAndLogEx(FAILED, "An error occurred while loading the dictionary!");
|
|
free(keyBlock_tmp);
|
|
free(*pkeyBlock);
|
|
return PM3_EFILE;
|
|
} else {
|
|
p = realloc(*pkeyBlock, (*pkeycnt + loaded_numKeys) * keysize);
|
|
if (p == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(keyBlock_tmp);
|
|
free(*pkeyBlock);
|
|
return PM3_EMALLOC;
|
|
}
|
|
*pkeyBlock = p;
|
|
memcpy(*pkeyBlock + *pkeycnt * keysize, keyBlock_tmp, loaded_numKeys * keysize);
|
|
*pkeycnt += loaded_numKeys;
|
|
free(keyBlock_tmp);
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int mfu_3pass_check_keys(uint8_t key_index, uint8_t firstChunk, uint8_t lastChunk,
|
|
uint32_t nkeys, int segment, uint8_t *ref_key, bool xor_ref_key, uint8_t *keyBlock,
|
|
bool verbose, bool quiet, uint32_t *auths, uint32_t *ms, bool check_answer, bool use_fastread0) {
|
|
// send keychunk
|
|
clearCommandBuffer();
|
|
|
|
mful_3passchk_t payload = {
|
|
.key_index = key_index,
|
|
.firstchunk = firstChunk,
|
|
.lastchunk = lastChunk,
|
|
.xor_ref_key = xor_ref_key,
|
|
.segment = segment != -1 ? segment : 4,
|
|
.check_answer = check_answer,
|
|
.use_fastread0 = use_fastread0,
|
|
.nkeys = nkeys
|
|
};
|
|
struct rp {
|
|
uint32_t auths;
|
|
uint32_t ticks;
|
|
uint8_t key[16];
|
|
} PACKED;
|
|
uint8_t keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE;
|
|
memcpy(payload.ref_key, ref_key, MIFAREU3P_KEY_SIZE);
|
|
if (nkeys * keysize > (uint32_t)(g_conn.max_cmd_data_size - MIFAREU3P_CHKKEY_HEADER)) {
|
|
PrintAndLogEx(ERR, "Key chunk size exceeds payload size");
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(payload.data, keyBlock, nkeys * keysize);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU3P_CHKKEY, (uint8_t *)&payload, MIFAREU3P_CHKKEY_HEADER + nkeys * keysize);
|
|
|
|
PacketResponseNG resp;
|
|
|
|
uint32_t timeout = 0;
|
|
while (WaitForResponseTimeout(CMD_HF_MIFAREU3P_CHKKEY, &resp, 2000) == false) {
|
|
|
|
while (kbd_enter_pressed()) {
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
PrintAndLogEx(INFO, "aborted via keyboard!");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (quiet == false) {
|
|
PrintAndLogEx((timeout) ? NORMAL : INFO, "." NOLF);
|
|
fflush(stdout);
|
|
}
|
|
|
|
timeout++;
|
|
|
|
// max timeout for one chunk of 85keys, 60*3sec = 180seconds
|
|
// s70 with 40*2 keys to check, 80*85 = 6800 auth.
|
|
// takes about 97s, still some margin before abort
|
|
// timeout = 180 => ~360s @ Mifare Classic 1k @ ~2300 keys in dict
|
|
// ~2300 keys @ Mifare Classic 1k => ~620s
|
|
if (timeout > 60 * 12) {
|
|
PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
}
|
|
|
|
if (timeout && (quiet == false)) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
// time to convert the returned data.
|
|
struct rp *rpayload = (struct rp *) resp.data.asBytes;
|
|
|
|
if (auths != NULL) {
|
|
*auths += rpayload->auths;
|
|
}
|
|
if (ms != NULL) {
|
|
*ms += rpayload->ticks;
|
|
}
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "Target key " _GREEN_("%1u") " -- found valid key [ " _GREEN_("%s") " ]",
|
|
key_index,
|
|
sprint_hex_inrow(rpayload->key, MIFAREU3P_KEY_SIZE)
|
|
);
|
|
}
|
|
return resp.status;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------
|
|
// Ultralight C Methods
|
|
//-------------------------------------------------------------------------------
|
|
|
|
static int stat_ulc_nonces(uint16_t num_sampled_nonces, uint64_t *nonces) {
|
|
for (uint16_t i = 0; i < num_sampled_nonces; i++) {
|
|
PrintAndLogEx(DEBUG, "Encrypted nonce: %016" PRIx64 "\n", nonces[i]);
|
|
}
|
|
|
|
// Count nonce frequencies
|
|
typedef struct {
|
|
uint64_t nonce;
|
|
uint32_t count;
|
|
} nonce_count_t;
|
|
|
|
nonce_count_t *counts = calloc(num_sampled_nonces, sizeof(nonce_count_t));
|
|
if (counts == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory for counts");
|
|
return PM3_EMALLOC;
|
|
}
|
|
uint32_t unique_count = 0;
|
|
uint32_t recurring_count = 0;
|
|
|
|
for (uint32_t i = 0; i < num_sampled_nonces; i++) {
|
|
bool found = false;
|
|
for (uint32_t j = 0; j < unique_count; j++) {
|
|
if (counts[j].nonce == nonces[i]) {
|
|
if (counts[j].count == 1) {
|
|
recurring_count++;
|
|
}
|
|
counts[j].count++;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found && unique_count < num_sampled_nonces) {
|
|
counts[unique_count].nonce = nonces[i];
|
|
counts[unique_count].count = 1;
|
|
unique_count++;
|
|
}
|
|
}
|
|
|
|
// Sort by count (descending)
|
|
for (uint32_t i = 0; i < unique_count - 1; i++) {
|
|
for (uint32_t j = i + 1; j < unique_count; j++) {
|
|
if (counts[j].count > counts[i].count) {
|
|
nonce_count_t temp = counts[i];
|
|
counts[i] = counts[j];
|
|
counts[j] = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show top N
|
|
uint8_t topn = 10;
|
|
uint32_t show_count = recurring_count < topn ? recurring_count : topn;
|
|
if (counts[0].count == 1) {
|
|
if (unique_count > 1) {
|
|
PrintAndLogEx(INFO, "All %u collected nonces are unique.", num_sampled_nonces);
|
|
}
|
|
free(counts);
|
|
return PM3_SUCCESS;
|
|
}
|
|
PrintAndLogEx(INFO, "Top %u most common nonces:", show_count);
|
|
for (uint32_t i = 0; i < show_count; i++) {
|
|
PrintAndLogEx(INFO, " %016" PRIx64 " (count: %u)", BSWAP_64(counts[i].nonce), counts[i].count);
|
|
}
|
|
free(counts);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// Ultralight C Authentication
|
|
//
|
|
static int CmdHF14AMfUCAuth(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu cauth",
|
|
"Tests 3DES key on Mifare Ultralight-C tag.\n"
|
|
"If key is not specified, a set of known defaults will be tried.",
|
|
"hf mfu cauth\n"
|
|
"hf mfu cauth --key 000102030405060708090a0b0c0d0e0f"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "key", "<hex>", "Authentication key (16 bytes in hex)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0("k", NULL, "Keep field on at the end (only if a key is provided)"),
|
|
arg_int0("r", "retries", "<n>", "Number of retries with provided key (def: 0)"),
|
|
arg_lit0("n", "nocheck", "Skip checking tag answer correctness (only if a key is provided)"),
|
|
arg_lit0("0", "read0", "Use fast READ0 (skip anticol)"),
|
|
arg_lit0(NULL, "noauth", "Skip authentication (when collecting nonces)"),
|
|
arg_lit0(NULL, "reset", "Reset field between each attempt"),
|
|
arg_lit0(NULL, "collect", "Collect nonces and show top 10"),
|
|
arg_strx0(NULL, "pair", "<ERndB:ERndARndBp>", "Nonce pair (option can be provided up to 10 times)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
bool keep_field_on = arg_get_lit(ctx, 3);
|
|
int retries = arg_get_int_def(ctx, 4, 0);
|
|
bool check_answer = !arg_get_lit(ctx, 5);
|
|
bool use_fastread0 = arg_get_lit(ctx, 6);
|
|
bool skip_auth = arg_get_lit(ctx, 7);
|
|
bool reset_field = arg_get_lit(ctx, 8);
|
|
bool collect_nonces = arg_get_lit(ctx, 9);
|
|
int available_pairs = 0;
|
|
int pairs_bytecount = 0;
|
|
uint8_t pairs[(8 + 16) * 10] = {0};
|
|
CLIGetHexWithReturn(ctx, 10, pairs, &pairs_bytecount);
|
|
CLIParserFree(ctx);
|
|
available_pairs = pairs_bytecount / (8 + 16);
|
|
if (available_pairs * (8 + 16) != pairs_bytecount) {
|
|
PrintAndLogEx(WARNING, "Invalid nonce pairs provided, byte count does not match expected size");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (available_pairs > 10) {
|
|
PrintAndLogEx(WARNING, "Too many nonce pairs provided");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (ak_len != 16 && ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (retries < 0 || retries > 10000) {
|
|
PrintAndLogEx(ERR, "Invalid retries (must be 0..10000)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if ((retries > 0) && (ak_len == 0) && !skip_auth && available_pairs == 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is required for retries");
|
|
return PM3_EINVARG;
|
|
}
|
|
if ((! check_answer) && (ak_len == 0)) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is required for nocheck");
|
|
return PM3_EINVARG;
|
|
}
|
|
if ((available_pairs > 0) && (ak_len > 0)) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key and pairs are mutually exclusive");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (skip_auth && !collect_nonces) {
|
|
PrintAndLogEx(WARNING, "ERROR: noauth option only valid with collect option");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint64_t *nonces = NULL;
|
|
if (collect_nonces) {
|
|
nonces = calloc(1 + retries, sizeof(uint64_t));
|
|
if (nonces == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian && ak_len) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, 16, 8);
|
|
}
|
|
|
|
int isok;
|
|
uint32_t auths = 0;
|
|
uint32_t ms = 0;
|
|
|
|
// If no hex key is specified, try default keys
|
|
if (ak_len == 0 && !collect_nonces && !skip_auth && available_pairs == 0) {
|
|
|
|
PrintAndLogEx(INFO, "Called with no key, checking default keys...");
|
|
isok = try_default_3des_keys(false, &auth_key_ptr, use_fastread0);
|
|
} else {
|
|
// try user-supplied
|
|
|
|
do {
|
|
uint16_t max_retries_per_call = retries;
|
|
if (collect_nonces) {
|
|
// Not strictly needed, but to avoid fw warning
|
|
max_retries_per_call = ((g_conn.max_cmd_data_size - sizeof(uint32_t) * 2) / sizeof(uint64_t)) - 1;
|
|
}
|
|
isok = ul3pass_authentication(auth_key_ptr, MIFAREULC_KEY_INDEX, !keep_field_on, MIN(retries - auths, max_retries_per_call), &auths, &ms, false, !skip_auth, check_answer, use_fastread0, collect_nonces, (uint8_t *)(nonces + auths), reset_field, available_pairs, pairs);
|
|
} while (skip_auth && auths < 1 + retries);
|
|
}
|
|
|
|
if (collect_nonces) {
|
|
stat_ulc_nonces(auths, nonces);
|
|
}
|
|
|
|
if (!skip_auth) {
|
|
if (isok == PM3_SUCCESS) {
|
|
if (available_pairs > 0) {
|
|
PrintAndLogEx(SUCCESS, "Authentication 3DES with nonce pair... " _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Authentication 3DES key... " _GREEN_("%s") " ( " _GREEN_("ok")" )", sprint_hex_inrow(auth_key_ptr, 16));
|
|
}
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Authentication ( " _RED_("fail") " )");
|
|
}
|
|
}
|
|
if (retries > 0) {
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0));
|
|
PrintAndLogEx(INFO, "Authentication attempts: %u", auths);
|
|
PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms));
|
|
}
|
|
free(nonces);
|
|
return isok;
|
|
}
|
|
|
|
static int CmdHF14AMfUCAuthChk(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu cchk",
|
|
"It checks MIFARE Ultralight C tags keys against a dictionary file with keys\n",
|
|
"hf mfu cchk -f mfulc_default_keys.dic");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "filename of dictionary"),
|
|
arg_int0("s", "segment", "<0..3>", "Segment index (full key if not specified)"),
|
|
arg_int0("r", "retries", "<0..255>", "Number of retries (def: 0)"),
|
|
arg_str0("k", "key", "<hex>", "Starting key, 16 hex bytes (def: zero key), for segment check"),
|
|
arg_lit0("x", "xor", "XOR starting key with segment candidates (def: override)"),
|
|
arg_lit0("n", "nocheck", "Skip checking tag answer correctness"),
|
|
arg_lit0("0", "read0", "Use fast READ0 (skip anticol)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
int segment = arg_get_int_def(ctx, 2, -1); // -1 means full key
|
|
int retries = arg_get_int_def(ctx, 3, 0);
|
|
int ref_keylen = 0;
|
|
uint8_t ref_key[16] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, ref_key, &ref_keylen);
|
|
bool xor_ref_key = arg_get_lit(ctx, 5);
|
|
bool check_answer = !arg_get_lit(ctx, 6);
|
|
bool use_fastread0 = arg_get_lit(ctx, 7);
|
|
CLIParserFree(ctx);
|
|
|
|
if (fnlen == 0) {
|
|
PrintAndLogEx(ERR, "No dictionary file specified");
|
|
return PM3_EFILE;
|
|
}
|
|
if (segment < -1 || segment > 3) {
|
|
PrintAndLogEx(ERR, "Invalid segment (must be 0..3)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (retries < 0 || retries > 255) {
|
|
PrintAndLogEx(ERR, "Invalid retries (must be 0..255)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (ref_keylen && ref_keylen != MIFAREU3P_KEY_SIZE) {
|
|
PrintAndLogEx(WARNING, "Key must be %i hex bytes. Got %d", MIFAREU3P_KEY_SIZE, ref_keylen);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (ref_keylen == 0) {
|
|
ref_keylen = MIFAREU3P_KEY_SIZE;
|
|
}
|
|
|
|
uint8_t *keyBlock = NULL;
|
|
uint32_t keycnt = 0;
|
|
int keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE;
|
|
int ret = mfu_3pass_load_keys(&keyBlock, &keycnt, filename, fnlen, keysize);
|
|
if (ret != PM3_SUCCESS) {
|
|
return ret;
|
|
}
|
|
if (keycnt == 0) {
|
|
PrintAndLogEx(ERR, "Dictionary contains no keys");
|
|
free(keyBlock);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// cap by what fits in one frame, then by what the nkeys field can announce
|
|
uint32_t max_chunk = (g_conn.max_cmd_data_size - MIFAREU3P_CHKKEY_HEADER) / keysize;
|
|
if (max_chunk > MIFAREU3P_CHKKEY_MAX_KEYS) {
|
|
max_chunk = MIFAREU3P_CHKKEY_MAX_KEYS;
|
|
}
|
|
uint32_t chunksize = (keycnt > max_chunk) ? max_chunk : keycnt;
|
|
bool firstChunk = true, lastChunk = false;
|
|
|
|
int i = 0;
|
|
|
|
// time
|
|
uint32_t auths = 0;
|
|
uint32_t ms = 0;
|
|
|
|
// main keychunk loop
|
|
for (int r = 0; r < retries + 1; r++) {
|
|
for (i = 0; i < keycnt; i += chunksize) {
|
|
if (kbd_enter_pressed()) {
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
SendCommandNG(CMD_FPGA_MAJOR_MODE_OFF, NULL, 0); // field is still ON if not on last chunk
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!");
|
|
goto out;
|
|
}
|
|
|
|
uint32_t nkeys = ((keycnt - i) > chunksize) ? chunksize : keycnt - i;
|
|
|
|
// last chunk?
|
|
if (nkeys == keycnt - i) {
|
|
lastChunk = true;
|
|
}
|
|
int res = mfu_3pass_check_keys(MIFAREULC_KEY_INDEX, firstChunk, lastChunk, nkeys, segment, ref_key, xor_ref_key, keyBlock + (i * keysize), false, true, &auths, &ms, check_answer, use_fastread0);
|
|
if (firstChunk)
|
|
firstChunk = false;
|
|
|
|
// all keys, aborted
|
|
if (res == PM3_SUCCESS || res == 2) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
goto out;
|
|
}
|
|
PrintAndLogEx(INPLACE, "Testing %5i/%5i ( " _YELLOW_("%02.1f %%") " )", i, keycnt, (float)i * 100 / keycnt);
|
|
} // end chunks of keys
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
out:
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0));
|
|
PrintAndLogEx(INFO, "Authentication attempts: %u", auths);
|
|
PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms));
|
|
|
|
free(keyBlock);
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
//-------------------------------------------------------------------------------
|
|
// Ultralight AES Methods
|
|
//-------------------------------------------------------------------------------
|
|
|
|
// Ultralight AES Authentication
|
|
//
|
|
static int CmdHF14AMfUAESAuth(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu aesauth",
|
|
"Tests AES key on Mifare Ultralight AES tags.\n"
|
|
"If no key is specified, null key will be tried.\n"
|
|
" Key index 0... DataProtKey (default)\n"
|
|
" Key index 1... UIDRetrKey\n"
|
|
" Key index 2... OriginalityKey\n",
|
|
"hf mfu aesauth\n"
|
|
"hf mfu aesauth --key <16 hex bytes> --idx <0..2>\n"
|
|
"hf mfu aesauth --key <16 hex bytes> --idx <0..2> --schann"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "key", "<hex>", "AES key (16 hex bytes)"),
|
|
arg_int0("i", "idx", "<0..2>", "Key index (def: 0)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0("k", NULL, "Keep field on (only if a key is provided)"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_int0("r", "retries", "<n>", "Number of retries (def: 0)"),
|
|
arg_lit0("n", "nocheck", "Skip checking tag answer correctness"),
|
|
arg_lit0("0", "read0", "Use fast READ0 (skip anticol)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authentication_key[16] = {0};
|
|
uint8_t *auth_key_ptr = authentication_key;
|
|
CLIGetHexWithReturn(ctx, 1, authentication_key, &ak_len);
|
|
int key_index = arg_get_int_def(ctx, 2, 0);
|
|
bool swap_endian = arg_get_lit(ctx, 3);
|
|
bool keep_field_on = arg_get_lit(ctx, 4);
|
|
bool use_schann = arg_get_lit(ctx, 5);
|
|
int retries = arg_get_int_def(ctx, 6, 0);
|
|
bool check_answer = !arg_get_lit(ctx, 7);
|
|
bool use_fastread0 = arg_get_lit(ctx, 8);
|
|
CLIParserFree(ctx);
|
|
|
|
if (ak_len == 0) {
|
|
// default to null key
|
|
ak_len = 16;
|
|
}
|
|
if (ak_len != 16) {
|
|
PrintAndLogEx(WARNING, "Invalid key length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (key_index < 0 || key_index > 2) {
|
|
PrintAndLogEx(WARNING, "Invalid key index");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian && ak_len) {
|
|
auth_key_ptr = SwapEndian64(authentication_key, ak_len, 16);
|
|
}
|
|
|
|
if (retries < 0 || retries > 10000) {
|
|
PrintAndLogEx(ERR, "Invalid retries (must be 0..10000)");
|
|
return PM3_EINVARG;
|
|
}
|
|
uint32_t auths = 0;
|
|
uint32_t ms = 0;
|
|
|
|
int result = ul3pass_authentication(auth_key_ptr, key_index, !keep_field_on, retries, &auths, &ms, use_schann, true, check_answer, use_fastread0, false, NULL, false, 0, NULL);
|
|
if (result == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Authentication with " _YELLOW_("%s") " " _GREEN_("%s") " ( " _GREEN_("ok")" )"
|
|
, key_type[key_index]
|
|
, sprint_hex_inrow(auth_key_ptr, ak_len)
|
|
);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Authentication with " _YELLOW_("%s") " ( " _RED_("fail") " )", key_type[key_index]);
|
|
}
|
|
if (retries > 0) {
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0));
|
|
PrintAndLogEx(INFO, "Authentication attempts: %u", auths);
|
|
PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static int CmdHF14AMfUAESAuthChk(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu aeschk",
|
|
"It checks MIFARE Ultralight AES tags keys against a dictionary file with keys\n"
|
|
" Key index 0... DataProtKey (default)\n"
|
|
" Key index 1... UIDRetrKey\n"
|
|
" Key index 2... OriginalityKey\n",
|
|
"hf mfu aeschk -f mfulaes_default_keys.dic");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "filename of dictionary"),
|
|
arg_int0("i", "idx", "<0..2>", "Key index (def: 0)"),
|
|
arg_int0("s", "segment", "<0..3>", "Segment index (full key if not specified)"),
|
|
arg_int0("r", "retries", "<0..255>", "Number of retries (def: 0)"),
|
|
arg_str0("k", "key", "<hex>", "Starting key, 16 hex bytes (def: zero key), for segment check"),
|
|
arg_lit0("x", "xor", "XOR starting key with segment candidates (def: override)"),
|
|
arg_lit0("n", "nocheck", "Skip checking tag answer correctness"),
|
|
arg_lit0("0", "read0", "Use fast READ0 (skip anticol)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
int key_index = arg_get_int_def(ctx, 2, 0);
|
|
int segment = arg_get_int_def(ctx, 3, -1); // -1 means full key
|
|
int retries = arg_get_int_def(ctx, 4, 0);
|
|
int ref_keylen = 0;
|
|
uint8_t ref_key[16] = {0};
|
|
CLIGetHexWithReturn(ctx, 5, ref_key, &ref_keylen);
|
|
bool xor_ref_key = arg_get_lit(ctx, 6);
|
|
bool check_answer = !arg_get_lit(ctx, 7);
|
|
bool use_fastread0 = arg_get_lit(ctx, 8);
|
|
CLIParserFree(ctx);
|
|
|
|
if (fnlen == 0) {
|
|
PrintAndLogEx(ERR, "No dictionary file specified");
|
|
return PM3_EFILE;
|
|
}
|
|
if (key_index < 0 || key_index > 2) {
|
|
PrintAndLogEx(ERR, "Invalid key index (must be 0..2)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (segment < -1 || segment > 3) {
|
|
PrintAndLogEx(ERR, "Invalid segment (must be 0..3)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (retries < 0 || retries > 255) {
|
|
PrintAndLogEx(ERR, "Invalid retries (must be 0..255)");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (ref_keylen && ref_keylen != MIFAREU3P_KEY_SIZE) {
|
|
PrintAndLogEx(WARNING, "Key must be %i hex bytes. Got %d", MIFAREU3P_KEY_SIZE, ref_keylen);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (ref_keylen == 0) {
|
|
ref_keylen = MIFAREU3P_KEY_SIZE;
|
|
}
|
|
|
|
uint8_t *keyBlock = NULL;
|
|
uint32_t keycnt = 0;
|
|
int keysize = segment != -1 ? MIFAREU3P_KEY_SIZE / 4 : MIFAREU3P_KEY_SIZE;
|
|
int ret = mfu_3pass_load_keys(&keyBlock, &keycnt, filename, fnlen, keysize);
|
|
if (ret != PM3_SUCCESS) {
|
|
return ret;
|
|
}
|
|
if (keycnt == 0) {
|
|
PrintAndLogEx(ERR, "Dictionary contains no keys");
|
|
free(keyBlock);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// cap by what fits in one frame, then by what the nkeys field can announce
|
|
uint32_t max_chunk = (g_conn.max_cmd_data_size - MIFAREU3P_CHKKEY_HEADER) / keysize;
|
|
if (max_chunk > MIFAREU3P_CHKKEY_MAX_KEYS) {
|
|
max_chunk = MIFAREU3P_CHKKEY_MAX_KEYS;
|
|
}
|
|
uint32_t chunksize = (keycnt > max_chunk) ? max_chunk : keycnt;
|
|
bool firstChunk = true, lastChunk = false;
|
|
|
|
int i = 0;
|
|
|
|
uint32_t auths = 0;
|
|
uint32_t ms = 0;
|
|
|
|
// main keychunk loop
|
|
for (int r = 0; r < retries + 1; r++) {
|
|
for (i = 0; i < keycnt; i += chunksize) {
|
|
if (kbd_enter_pressed()) {
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
SendCommandNG(CMD_FPGA_MAJOR_MODE_OFF, NULL, 0); // field is still ON if not on last chunk
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!");
|
|
goto out;
|
|
}
|
|
|
|
uint32_t nkeys = ((keycnt - i) > chunksize) ? chunksize : keycnt - i;
|
|
|
|
// last chunk?
|
|
if (nkeys == keycnt - i) {
|
|
lastChunk = true;
|
|
}
|
|
|
|
int res = mfu_3pass_check_keys(key_index, firstChunk, lastChunk, nkeys, segment, ref_key, xor_ref_key, keyBlock + (i * keysize), false, true, &auths, &ms, check_answer, use_fastread0);
|
|
if (firstChunk)
|
|
firstChunk = false;
|
|
|
|
// all keys, aborted
|
|
if (res == PM3_SUCCESS || res == 2) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
goto out;
|
|
}
|
|
PrintAndLogEx(INPLACE, "Testing %5i/%5i ( " _YELLOW_("%02.1f %%") " )", i, keycnt, (float)i * 100 / keycnt);
|
|
} // end chunks of keys
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
out:
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0));
|
|
PrintAndLogEx(INFO, "Authentication attempts: %u", auths);
|
|
PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms));
|
|
|
|
free(keyBlock);
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfUAESGetUID(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu aesgetuid",
|
|
"Retreives real UID on Mifare Ultralight AES tags when random ID is enabled.\n"
|
|
"Uses key index 1 (UIDRetrKey).\n"
|
|
"If no key is specified, null key will be tried.\n",
|
|
"hf mfu aesgetuid\n"
|
|
"hf mfu aesgetuid --key <16 hex bytes>\n"
|
|
"hf mfu aesgetuid --key <16 hex bytes> --schann"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "key", "<hex>", "AES key (16 hex bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_int0("r", "retries", "<n>", "Number of retries (def: 0)"),
|
|
arg_lit0("n", "nocheck", "Skip checking tag answer correctness"),
|
|
arg_lit0("0", "read0", "Use fast READ0 (skip anticol)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authentication_key[16] = {0};
|
|
uint8_t *auth_key_ptr = authentication_key;
|
|
CLIGetHexWithReturn(ctx, 1, authentication_key, &ak_len);
|
|
int key_index = 1;
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
bool keep_field_on = true;
|
|
bool use_schann = arg_get_lit(ctx, 3);
|
|
int retries = arg_get_int_def(ctx, 4, 0);
|
|
bool check_answer = !arg_get_lit(ctx, 5);
|
|
bool use_fastread0 = arg_get_lit(ctx, 6);
|
|
CLIParserFree(ctx);
|
|
|
|
if (ak_len == 0) {
|
|
// default to null key
|
|
ak_len = 16;
|
|
}
|
|
if (ak_len != 16) {
|
|
PrintAndLogEx(WARNING, "Invalid key length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (key_index < 0 || key_index > 2) {
|
|
PrintAndLogEx(WARNING, "Invalid key index");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian && ak_len) {
|
|
auth_key_ptr = SwapEndian64(authentication_key, ak_len, 16);
|
|
}
|
|
|
|
if (retries < 0 || retries > 10000) {
|
|
PrintAndLogEx(ERR, "Invalid retries (must be 0..10000)");
|
|
return PM3_EINVARG;
|
|
}
|
|
uint32_t auths = 0;
|
|
uint32_t ms = 0;
|
|
|
|
int result = ul3pass_authentication(auth_key_ptr, key_index, !keep_field_on, retries, &auths, &ms, use_schann, true, check_answer, use_fastread0, false, NULL, false, 0, NULL);
|
|
if (result == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Authentication with " _YELLOW_("%s") " " _GREEN_("%s") " ( " _GREEN_("ok")" )"
|
|
, key_type[key_index]
|
|
, sprint_hex_inrow(auth_key_ptr, ak_len)
|
|
);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Authentication with " _YELLOW_("%s") " ( " _RED_("fail") " )", key_type[key_index]);
|
|
return PM3_ESOFT;
|
|
}
|
|
if (retries > 0) {
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), (float)(ms / 1000.0));
|
|
PrintAndLogEx(INFO, "Authentication attempts: %u", auths);
|
|
PrintAndLogEx(INFO, "Speed: %.1f auths/s", (float)(auths * 1000.0 / ms));
|
|
}
|
|
|
|
uint8_t data[8] = {0x00};
|
|
int status = ul_read(0, data, sizeof(data), use_schann);
|
|
if (status <= 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ %i", status);
|
|
return PM3_ESOFT;
|
|
}
|
|
uint8_t uid[7] = {data[0], data[1], data[2], data[4], data[5], data[6], data[7]};
|
|
PrintAndLogEx(SUCCESS, "UID: " _GREEN_("%s"), sprint_hex(uid, 7));
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//DESBRUTE Translation: C2Pwn
|
|
|
|
typedef enum {
|
|
MFULC_DESBRUTE_LFSR_UNDEF = 0,
|
|
MFULC_DESBRUTE_LFSR_ULCG = 1,
|
|
MFULC_DESBRUTE_LFSR_MFC = 2,
|
|
} mfulc_desbrute_lfsr_t;
|
|
|
|
typedef struct {
|
|
uint32_t start;
|
|
uint32_t end;
|
|
int key_mode;
|
|
bool candidate_in_k1;
|
|
int var_offset;
|
|
uint8_t init_ciphertext[8];
|
|
uint8_t prev_ciphertext[8];
|
|
uint8_t ciphertext[8];
|
|
uint64_t init_ip_block;
|
|
uint64_t prev_ciphertext_be;
|
|
uint64_t ciphertext_ip_block;
|
|
uint8_t base_key[16];
|
|
uint64_t fixed_sk[16];
|
|
uint64_t cand_sk_base[16];
|
|
uint64_t cand_sk_contrib[28 * 16];
|
|
mfulc_desbrute_lfsr_t lfsr_type;
|
|
bool is_reader_mode;
|
|
int thread_id;
|
|
} mfulc_desbrute_thread_args_t;
|
|
|
|
typedef struct {
|
|
volatile bool found;
|
|
volatile bool aborted;
|
|
uint32_t found_idx;
|
|
uint8_t found_key[16];
|
|
} mfulc_desbrute_shared_t;
|
|
|
|
typedef struct {
|
|
mfulc_desbrute_thread_args_t args;
|
|
mfulc_desbrute_shared_t *shared;
|
|
volatile uint32_t progress;
|
|
volatile bool done;
|
|
} mfulc_desbrute_worker_args_t;
|
|
|
|
static uint64_t mfulc_desbrute_be64(const uint8_t *b) {
|
|
return ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) |
|
|
((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32) |
|
|
((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
|
|
((uint64_t)b[6] << 8) | (uint64_t)b[7];
|
|
}
|
|
|
|
static const uint8_t MFULC_DES_PC1[56] = {
|
|
57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18,
|
|
10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
|
|
63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
|
|
14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4
|
|
};
|
|
|
|
static const uint8_t MFULC_DES_PC2[48] = {
|
|
14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
|
|
23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
|
|
41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
|
|
44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32
|
|
};
|
|
|
|
static const uint8_t MFULC_DES_SHIFTS[16] = {
|
|
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
|
|
};
|
|
|
|
static const uint8_t MFULC_DES_IP[64] = {
|
|
58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
|
|
62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
|
|
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7
|
|
};
|
|
|
|
static const uint8_t MFULC_DES_FP[64] = {
|
|
40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
|
|
38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
|
|
36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
|
|
34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25
|
|
};
|
|
|
|
static const uint32_t MFULC_DES_SP[512] = {
|
|
0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, 0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002, 0x00000202, 0x00800200, 0x00800200, 0x00008200, 0x00008200, 0x00808000, 0x00808000, 0x00800202, 0x00008002, 0x00800002, 0x00800002, 0x00008002, 0x00000000, 0x00000202, 0x00008202, 0x00800000, 0x00008000, 0x00808202, 0x00000002, 0x00808000, 0x00808200, 0x00800000, 0x00800000, 0x00000200, 0x00808002, 0x00008000, 0x00008200, 0x00800002, 0x00000200, 0x00000002, 0x00800202, 0x00008202, 0x00808202, 0x00008002, 0x00808000, 0x00800202, 0x00800002, 0x00000202, 0x00008202, 0x00808200, 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002,
|
|
0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, 0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010, 0x00084000, 0x00080010, 0x40004010, 0x00000000, 0x40000000, 0x00004000, 0x00084010, 0x40080000, 0x00080010, 0x40000010, 0x00000000, 0x00084000, 0x00004010, 0x40084000, 0x40080000, 0x00004010, 0x00000000, 0x00084010, 0x40080010, 0x00080000, 0x40004010, 0x40080000, 0x40084000, 0x00004000, 0x40080000, 0x40004000, 0x00000010, 0x40084010, 0x00084010, 0x00000010, 0x00004000, 0x40000000, 0x00004010, 0x40084000, 0x00080000, 0x40000010, 0x00080010, 0x40004010, 0x40000010, 0x00080010, 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000,
|
|
0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, 0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104, 0x04000000, 0x00000004, 0x04010100, 0x00000100, 0x00010100, 0x04010000, 0x04010004, 0x00010104, 0x04000104, 0x00010100, 0x00010000, 0x04000104, 0x00000004, 0x04010104, 0x00000100, 0x04000000, 0x04010100, 0x04000000, 0x00010004, 0x00000104, 0x00010000, 0x04010100, 0x04000100, 0x00000000, 0x00000100, 0x00010004, 0x04010104, 0x04000100, 0x04000004, 0x00000100, 0x00000000, 0x04010004, 0x04000104, 0x00010000, 0x04000000, 0x04010104, 0x00000004, 0x00010104, 0x00010100, 0x04000004, 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100,
|
|
0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, 0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000, 0x80000000, 0x00001000, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x80001000, 0x00001040, 0x80400040, 0x80000000, 0x00001040, 0x00400040, 0x00001000, 0x00401040, 0x80401040, 0x80000040, 0x00400040, 0x80400000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00000000, 0x00401000, 0x00001040, 0x00400040, 0x80400040, 0x80000000, 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x80401040, 0x80000040, 0x80000000, 0x00001000, 0x80400000, 0x80001000, 0x00401040, 0x80400040, 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040,
|
|
0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, 0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000, 0x01000000, 0x20040000, 0x20040000, 0x00000000, 0x20000080, 0x21040080, 0x21040080, 0x01000080, 0x21040000, 0x20000080, 0x00000000, 0x21000000, 0x01040080, 0x01000000, 0x21000000, 0x00040080, 0x00040000, 0x21000080, 0x00000080, 0x01000000, 0x20000000, 0x01040000, 0x21000080, 0x20040080, 0x01000080, 0x20000000, 0x21040000, 0x01040080, 0x20040080, 0x00000080, 0x01000000, 0x21040000, 0x21040080, 0x00040080, 0x21000000, 0x21040080, 0x01040000, 0x00000000, 0x20040000, 0x21000000, 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080,
|
|
0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, 0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008, 0x00000000, 0x00200008, 0x10002008, 0x00002000, 0x00202000, 0x10002008, 0x00000008, 0x10200008, 0x10200008, 0x00000000, 0x00202008, 0x10202000, 0x00002008, 0x00202000, 0x10202000, 0x10000000, 0x10002000, 0x00000008, 0x10200008, 0x00202000, 0x10202008, 0x00200000, 0x00002008, 0x10000008, 0x00200000, 0x10002000, 0x10000000, 0x00002008, 0x10000008, 0x10202008, 0x00202000, 0x10200000, 0x00202008, 0x10202000, 0x00000000, 0x10200008, 0x00000008, 0x00002000, 0x10200000, 0x00202008, 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008,
|
|
0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, 0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401, 0x02000400, 0x00100401, 0x00100001, 0x02000400, 0x02000001, 0x02100000, 0x02100400, 0x00100001, 0x02100000, 0x00000400, 0x00000401, 0x02100401, 0x00100400, 0x00000001, 0x02000000, 0x00100400, 0x02000000, 0x00100400, 0x00100000, 0x02000401, 0x02000401, 0x02100001, 0x02100001, 0x00000001, 0x00100001, 0x02000000, 0x02000400, 0x00100000, 0x02100400, 0x00000401, 0x00100401, 0x02100400, 0x00000401, 0x02000001, 0x02100401, 0x02100000, 0x00100400, 0x00000000, 0x00000001, 0x02100401, 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001,
|
|
0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, 0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020, 0x08020000, 0x08000020, 0x08000800, 0x00000820, 0x00020800, 0x00020020, 0x08020020, 0x08020800, 0x00000820, 0x00000000, 0x00000000, 0x08020020, 0x08000020, 0x08000800, 0x00020820, 0x00020000, 0x00020820, 0x00020000, 0x08020800, 0x00000800, 0x00000020, 0x08020020, 0x00000800, 0x00020820, 0x08000800, 0x00000020, 0x08000020, 0x08020000, 0x08020020, 0x08000000, 0x00020000, 0x08000820, 0x00000000, 0x08020820, 0x00020020, 0x08000020, 0x08020000, 0x08000800, 0x08000820, 0x00000000, 0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800
|
|
};
|
|
|
|
static uint64_t mfulc_desbrute_perm(uint64_t src, int src_bits, const uint8_t *tbl, int n) {
|
|
uint64_t dst = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
int sb = tbl[i] - 1;
|
|
dst |= ((src >> (src_bits - 1 - sb)) & 1ULL) << (n - 1 - i);
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
static void mfulc_desbrute_keyschedule(uint64_t key64, uint64_t sk[16]) {
|
|
uint64_t key56 = mfulc_desbrute_perm(key64, 64, MFULC_DES_PC1, 56);
|
|
uint32_t c = (uint32_t)(key56 >> 28) & 0x0FFFFFFF;
|
|
uint32_t d = (uint32_t)key56 & 0x0FFFFFFF;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
int s = MFULC_DES_SHIFTS[i];
|
|
c = ((c << s) | (c >> (28 - s))) & 0x0FFFFFFF;
|
|
d = ((d << s) | (d >> (28 - s))) & 0x0FFFFFFF;
|
|
sk[i] = mfulc_desbrute_perm(((uint64_t)c << 28) | d, 56, MFULC_DES_PC2, 48);
|
|
}
|
|
}
|
|
|
|
static uint32_t mfulc_desbrute_f(uint32_t r, uint64_t k) {
|
|
uint32_t r0 = ((r & 1u) << 5) | ((r >> 27) & 0x1Fu);
|
|
uint32_t r1 = (r >> 23) & 0x3Fu;
|
|
uint32_t r2 = (r >> 19) & 0x3Fu;
|
|
uint32_t r3 = (r >> 15) & 0x3Fu;
|
|
uint32_t r4 = (r >> 11) & 0x3Fu;
|
|
uint32_t r5 = (r >> 7) & 0x3Fu;
|
|
uint32_t r6 = (r >> 3) & 0x3Fu;
|
|
uint32_t r7 = ((r & 0x1Fu) << 1) | ((r >> 31) & 1u);
|
|
|
|
r0 ^= (uint32_t)((k >> 42) & 0x3Fu);
|
|
r1 ^= (uint32_t)((k >> 36) & 0x3Fu);
|
|
r2 ^= (uint32_t)((k >> 30) & 0x3Fu);
|
|
r3 ^= (uint32_t)((k >> 24) & 0x3Fu);
|
|
r4 ^= (uint32_t)((k >> 18) & 0x3Fu);
|
|
r5 ^= (uint32_t)((k >> 12) & 0x3Fu);
|
|
r6 ^= (uint32_t)((k >> 6) & 0x3Fu);
|
|
r7 ^= (uint32_t)(k & 0x3Fu);
|
|
|
|
return MFULC_DES_SP[r0] ^ MFULC_DES_SP[64 + r1] ^ MFULC_DES_SP[128 + r2] ^ MFULC_DES_SP[192 + r3] ^
|
|
MFULC_DES_SP[256 + r4] ^ MFULC_DES_SP[320 + r5] ^ MFULC_DES_SP[384 + r6] ^ MFULC_DES_SP[448 + r7];
|
|
}
|
|
|
|
static uint64_t mfulc_desbrute_des_rounds(uint64_t ip_block, const uint64_t sk[16], bool decrypt) {
|
|
uint32_t l = (uint32_t)(ip_block >> 32);
|
|
uint32_t r = (uint32_t)ip_block;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
uint64_t k = decrypt ? sk[15 - i] : sk[i];
|
|
uint32_t nr = l ^ mfulc_desbrute_f(r, k);
|
|
l = r;
|
|
r = nr;
|
|
}
|
|
return ((uint64_t)r << 32) | l;
|
|
}
|
|
|
|
static uint64_t mfulc_desbrute_des(uint64_t block, const uint64_t sk[16], bool decrypt) {
|
|
uint64_t t = mfulc_desbrute_perm(block, 64, MFULC_DES_IP, 64);
|
|
t = mfulc_desbrute_des_rounds(t, sk, decrypt);
|
|
return mfulc_desbrute_perm(t, 64, MFULC_DES_FP, 64);
|
|
}
|
|
|
|
static uint64_t mfulc_desbrute_tdea2_dec_ip(uint64_t ip_block, const uint64_t k1_sk[16], const uint64_t k2_sk[16]) {
|
|
uint64_t t = mfulc_desbrute_des_rounds(ip_block, k1_sk, true);
|
|
t = mfulc_desbrute_des_rounds(t, k2_sk, false);
|
|
t = mfulc_desbrute_des_rounds(t, k1_sk, true);
|
|
return mfulc_desbrute_perm(t, 64, MFULC_DES_FP, 64);
|
|
}
|
|
|
|
static void mfulc_desbrute_format_duration(uint64_t seconds, char *buf, size_t buflen) {
|
|
unsigned int h = (unsigned int)(seconds / 3600);
|
|
unsigned int m = (unsigned int)((seconds / 60) % 60);
|
|
unsigned int s = (unsigned int)(seconds % 60);
|
|
|
|
if (h > 99) {
|
|
snprintf(buf, buflen, ">99h");
|
|
} else if (h > 0) {
|
|
snprintf(buf, buflen, "%02u:%02u:%02u", h, m, s);
|
|
} else {
|
|
snprintf(buf, buflen, "%02u:%02u", m, s);
|
|
}
|
|
}
|
|
|
|
static void mfulc_desbrute_progress_bar(double pct, char *buf, size_t buflen) {
|
|
const int width = 28;
|
|
int filled = (int)((pct / 100.0) * width);
|
|
|
|
if (filled < 0) filled = 0;
|
|
if (filled > width) filled = width;
|
|
if (buflen < (size_t)width + 3) {
|
|
if (buflen > 0) buf[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
buf[0] = '[';
|
|
for (int i = 0; i < width; i++) {
|
|
buf[i + 1] = i < filled ? '#' : '.';
|
|
}
|
|
buf[width + 1] = ']';
|
|
buf[width + 2] = '\0';
|
|
}
|
|
|
|
static const char *mfulc_desbrute_progress_color(double pct) {
|
|
if (pct >= 90.0) {
|
|
return "\x1b[32m";
|
|
}
|
|
if (pct >= 50.0) {
|
|
return "\x1b[33m";
|
|
}
|
|
return "\x1b[36m";
|
|
}
|
|
|
|
static void mfulc_desbrute_compute_sk_tables(const uint8_t base_half[8], int var_offset, uint64_t sk_base[16], uint64_t sk_contrib[28 * 16]) {
|
|
uint8_t half[8] = {0};
|
|
|
|
memcpy(half, base_half, sizeof(half));
|
|
half[var_offset] = 0;
|
|
half[var_offset + 1] = 0;
|
|
half[var_offset + 2] = 0;
|
|
half[var_offset + 3] = 0;
|
|
mfulc_desbrute_keyschedule(mfulc_desbrute_be64(half), sk_base);
|
|
|
|
for (int bit = 0; bit < 28; bit++) {
|
|
uint8_t tmp[8] = {0};
|
|
int byte_in_half = bit / 7;
|
|
int bit_in_byte = bit % 7;
|
|
uint64_t bit_sk[16] = {0};
|
|
|
|
tmp[var_offset + byte_in_half] = (uint8_t)(1u << (bit_in_byte + 1));
|
|
mfulc_desbrute_keyschedule(mfulc_desbrute_be64(tmp), bit_sk);
|
|
|
|
for (int round = 0; round < 16; round++) {
|
|
sk_contrib[(bit * 16) + round] = bit_sk[round];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mfulc_desbrute_make_candidate_sk(const mfulc_desbrute_thread_args_t *args, uint32_t idx, uint64_t cand_sk[16]) {
|
|
memcpy(cand_sk, args->cand_sk_base, sizeof(args->cand_sk_base));
|
|
|
|
for (int bit = 0; bit < 28; bit++) {
|
|
if ((idx >> bit) & 1u) {
|
|
const uint64_t *contrib = &args->cand_sk_contrib[bit * 16];
|
|
for (int round = 0; round < 16; round++) {
|
|
cand_sk[round] ^= contrib[round];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mfulc_desbrute_update_candidate_sk(const mfulc_desbrute_thread_args_t *args, uint32_t old_idx, uint32_t new_idx, uint64_t cand_sk[16]) {
|
|
uint32_t changed = old_idx ^ new_idx;
|
|
|
|
while (changed != 0) {
|
|
int bit = __builtin_ctz(changed);
|
|
const uint64_t *contrib = &args->cand_sk_contrib[bit * 16];
|
|
|
|
for (int round = 0; round < 16; round++) {
|
|
cand_sk[round] ^= contrib[round];
|
|
}
|
|
changed &= changed - 1;
|
|
}
|
|
}
|
|
|
|
static bool mfulc_desbrute_valid_lfsr_ulcg(uint64_t x64) {
|
|
x64 = BSWAP_64(x64);
|
|
uint16_t x16 = x64 >> 48;
|
|
x16 = (uint16_t)(x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1)));
|
|
if (x16 != ((x64 >> 32) & 0xFFFF)) return false;
|
|
x16 = (uint16_t)(x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1)));
|
|
if (x16 != ((x64 >> 16) & 0xFFFF)) return false;
|
|
x16 = (uint16_t)(x16 << 15 | ((x16 >> 1) ^ ((x16 >> 3 ^ x16 >> 4 ^ x16 >> 6) & 1)));
|
|
return x16 == (x64 & 0xFFFF);
|
|
}
|
|
|
|
static bool mfulc_desbrute_valid_lfsr_mfc(uint64_t x64) {
|
|
x64 = BSWAP_64(x64);
|
|
uint16_t x16 = x64 & 0xFFFF;
|
|
for (int i = 0; i < 16; i++) x16 = (uint16_t)(x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15);
|
|
if (x16 != ((x64 >> 16) & 0xFFFF)) return false;
|
|
for (int i = 0; i < 16; i++) x16 = (uint16_t)(x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15);
|
|
if (x16 != ((x64 >> 32) & 0xFFFF)) return false;
|
|
for (int i = 0; i < 16; i++) x16 = (uint16_t)(x16 >> 1 | (x16 ^ x16 >> 2 ^ x16 >> 3 ^ x16 >> 5) << 15);
|
|
return x16 == ((x64 >> 48) & 0xFFFF);
|
|
}
|
|
|
|
static bool mfulc_desbrute_valid_lfsr(uint64_t x64, mfulc_desbrute_lfsr_t lfsr_type) {
|
|
switch (lfsr_type) {
|
|
case MFULC_DESBRUTE_LFSR_ULCG:
|
|
return mfulc_desbrute_valid_lfsr_ulcg(x64);
|
|
case MFULC_DESBRUTE_LFSR_MFC:
|
|
return mfulc_desbrute_valid_lfsr_mfc(x64);
|
|
case MFULC_DESBRUTE_LFSR_UNDEF:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static mfulc_desbrute_lfsr_t mfulc_desbrute_detect_lfsr_type(const uint8_t init_ciphertext[8]) {
|
|
uint64_t zero_sk[16] = {0};
|
|
uint64_t x_be;
|
|
|
|
mfulc_desbrute_keyschedule(0, zero_sk);
|
|
x_be = mfulc_desbrute_des(mfulc_desbrute_be64(init_ciphertext), zero_sk, true);
|
|
uint64_t x = BSWAP_64(x_be);
|
|
if (mfulc_desbrute_valid_lfsr_ulcg(x)) {
|
|
return MFULC_DESBRUTE_LFSR_ULCG;
|
|
}
|
|
if (mfulc_desbrute_valid_lfsr_mfc(x)) {
|
|
return MFULC_DESBRUTE_LFSR_MFC;
|
|
}
|
|
return MFULC_DESBRUTE_LFSR_UNDEF;
|
|
}
|
|
|
|
static void mfulc_desbrute_fill_candidate(uint8_t key[16], const uint8_t base_key[16], int key_mode, uint32_t idx) {
|
|
memcpy(key, base_key, 16);
|
|
int seg_offset = key_mode * 4;
|
|
key[seg_offset] = (uint8_t)(((idx) & 0x7F) << 1);
|
|
key[seg_offset + 1] = (uint8_t)(((idx >> 7) & 0x7F) << 1);
|
|
key[seg_offset + 2] = (uint8_t)(((idx >> 14) & 0x7F) << 1);
|
|
key[seg_offset + 3] = (uint8_t)(((idx >> 21) & 0x7F) << 1);
|
|
}
|
|
|
|
static void mfulc_desbrute_candidate_batch(uint32_t start, uint32_t idx[4]) {
|
|
union vec lanes = vec_uadd(vec_u1(start), vec_u(0, 1, 2, 3));
|
|
for (int i = 0; i < 4; i++) {
|
|
idx[i] = lanes.elem.u[i];
|
|
}
|
|
}
|
|
|
|
static bool mfulc_desbrute_test_candidate_sk(const mfulc_desbrute_thread_args_t *args, const uint64_t cand_sk[16]) {
|
|
uint64_t out_be;
|
|
const uint64_t *k1_sk;
|
|
const uint64_t *k2_sk;
|
|
|
|
k1_sk = args->candidate_in_k1 ? cand_sk : args->fixed_sk;
|
|
k2_sk = args->candidate_in_k1 ? args->fixed_sk : cand_sk;
|
|
out_be = mfulc_desbrute_tdea2_dec_ip(args->ciphertext_ip_block, k1_sk, k2_sk);
|
|
|
|
bool match = false;
|
|
if (args->is_reader_mode) {
|
|
uint64_t init_be = mfulc_desbrute_tdea2_dec_ip(args->init_ip_block, k1_sk, k2_sk);
|
|
uint64_t rotated_init_be = (init_be << 8) | (init_be >> 56);
|
|
|
|
match = (out_be ^ args->prev_ciphertext_be) == rotated_init_be;
|
|
} else {
|
|
match = mfulc_desbrute_valid_lfsr(BSWAP_64(out_be), args->lfsr_type);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static void *mfulc_desbrute_worker(void *arg) {
|
|
mfulc_desbrute_worker_args_t *ctx = arg;
|
|
|
|
uint32_t candidate[4] = {0};
|
|
uint32_t last_candidate = 0;
|
|
uint64_t cand_sk[16] = {0};
|
|
bool have_candidate_sk = false;
|
|
|
|
ctx->progress = ctx->args.start;
|
|
for (uint32_t idx = ctx->args.start; idx < ctx->args.end; idx += 4) {
|
|
if (ctx->shared->found || ctx->shared->aborted) {
|
|
break;
|
|
}
|
|
if ((idx & 0x3FF) == 0) {
|
|
ctx->progress = idx;
|
|
}
|
|
if (ctx->args.thread_id == 0 && ((idx & 0x3FFFF) == 0) && kbd_enter_pressed()) {
|
|
ctx->shared->aborted = true;
|
|
break;
|
|
}
|
|
|
|
mfulc_desbrute_candidate_batch(idx, candidate);
|
|
for (int lane = 0; lane < 4; lane++) {
|
|
if (candidate[lane] >= ctx->args.end) {
|
|
break;
|
|
}
|
|
|
|
if (have_candidate_sk) {
|
|
mfulc_desbrute_update_candidate_sk(&ctx->args, last_candidate, candidate[lane], cand_sk);
|
|
} else {
|
|
mfulc_desbrute_make_candidate_sk(&ctx->args, candidate[lane], cand_sk);
|
|
have_candidate_sk = true;
|
|
}
|
|
last_candidate = candidate[lane];
|
|
|
|
if (mfulc_desbrute_test_candidate_sk(&ctx->args, cand_sk)) {
|
|
ctx->shared->found_idx = candidate[lane];
|
|
mfulc_desbrute_fill_candidate(ctx->shared->found_key, ctx->args.base_key, ctx->args.key_mode, candidate[lane]);
|
|
ctx->progress = candidate[lane] + 1;
|
|
ctx->shared->found = true;
|
|
ctx->done = true;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
ctx->progress = ctx->shared->found || ctx->shared->aborted ? ctx->progress : ctx->args.end;
|
|
ctx->done = true;
|
|
return NULL;
|
|
}
|
|
|
|
static int CmdHF14AMfUCDesBrute(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu desbrute",
|
|
"Recover one 4-byte segment of a MIFARE Ultralight-C 2TDEA key from known authentication ciphertexts.",
|
|
"hf mfu desbrute --counterfeit --null F35C740106ECED87 --target E9E0DC67B35919FC --key 00000000000000000000000000000000 --segment 2\n"
|
|
"hf mfu desbrute --counterfeit --null 49C1603621CCAA72 --target 8122262EF5FA8DEB --key 48444C4A4044524200000000544E5846 --segment 3\n"
|
|
"hf mfu desbrute --reader --erndb EC9C5CF763244367 --cryptogram 2283BFE8DEBE1780922327794D0706EF --key 48444C4A4044524200000000544E5846 --segment 3");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("c", "counterfeit", "Counterfeit nonce mode: --null and --target are ERndB blocks"),
|
|
arg_lit0("r", "reader", "Reader nonce mode: --erndb and --cryptogram are sniffed authentication blocks"),
|
|
arg_str0(NULL, "null", "<hex>", "Null-key ERndB, 8 hex bytes"),
|
|
arg_str0(NULL, "target", "<hex>", "Target-key ERndB, 8 hex bytes"),
|
|
arg_str0(NULL, "erndb", "<hex>", "Reader mode ERndB, 8 hex bytes"),
|
|
arg_str0(NULL, "cryptogram", "<hex>", "Reader mode ERndA|ERndB', 16 hex bytes"),
|
|
arg_str1("k", "key", "<hex>", "Base 3DES key, 16 hex bytes"),
|
|
arg_int1("s", "segment", "<1..4>", "4-byte key segment to brute force"),
|
|
arg_int0("t", "threads", "<n>", "Worker threads (default: all logical CPUs)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool counterfeit_mode = arg_get_lit(ctx, 1);
|
|
bool reader_mode = arg_get_lit(ctx, 2);
|
|
uint8_t init_ciphertext[8] = {0};
|
|
uint8_t ciphertext[8] = {0};
|
|
uint8_t prev_ciphertext[8] = {0};
|
|
uint8_t tmp_blocks[16] = {0};
|
|
uint8_t base_key[16] = {0};
|
|
int init_len = 0;
|
|
int ciphertext_len = 0;
|
|
int tmp_len = 0;
|
|
int key_len = 0;
|
|
|
|
if (counterfeit_mode) {
|
|
CLIGetHexWithReturn(ctx, 3, init_ciphertext, &init_len);
|
|
CLIGetHexWithReturn(ctx, 4, ciphertext, &ciphertext_len);
|
|
}
|
|
if (reader_mode) {
|
|
CLIGetHexWithReturn(ctx, 5, init_ciphertext, &init_len);
|
|
CLIGetHexWithReturn(ctx, 6, tmp_blocks, &tmp_len);
|
|
memcpy(prev_ciphertext, tmp_blocks, 8);
|
|
memcpy(ciphertext, tmp_blocks + 8, 8);
|
|
}
|
|
CLIGetHexWithReturn(ctx, 7, base_key, &key_len);
|
|
int segment = arg_get_int_def(ctx, 8, 0);
|
|
int threads = arg_get_int_def(ctx, 9, num_CPUs());
|
|
CLIParserFree(ctx);
|
|
|
|
if (counterfeit_mode == reader_mode) {
|
|
PrintAndLogEx(WARNING, "Select exactly one mode: --counterfeit or --reader");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (init_len != 8 || key_len != 16 || (counterfeit_mode && ciphertext_len != 8) || (reader_mode && tmp_len != 16)) {
|
|
PrintAndLogEx(WARNING, "Invalid input length. Blocks are 8 bytes, reader cryptogram is 16 bytes, key is 16 bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (segment < 1 || segment > 4) {
|
|
PrintAndLogEx(WARNING, "Segment must be 1..4");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (threads < 1) {
|
|
threads = 1;
|
|
}
|
|
int max_threads = num_CPUs();
|
|
if (threads > max_threads) {
|
|
PrintAndLogEx(INFO, "Capping threads at available CPU count (%d)", max_threads);
|
|
threads = max_threads;
|
|
}
|
|
|
|
mfulc_desbrute_lfsr_t lfsr_type = MFULC_DESBRUTE_LFSR_UNDEF;
|
|
if (counterfeit_mode) {
|
|
lfsr_type = mfulc_desbrute_detect_lfsr_type(init_ciphertext);
|
|
if (lfsr_type == MFULC_DESBRUTE_LFSR_UNDEF) {
|
|
PrintAndLogEx(WARNING, "LFSR detection failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
PrintAndLogEx(INFO, "LFSR detection: %s", lfsr_type == MFULC_DESBRUTE_LFSR_ULCG ? "ULCG" : "MFC (USCUID-UL/FJ8010)");
|
|
}
|
|
|
|
pthread_t *tids = calloc(threads, sizeof(pthread_t));
|
|
mfulc_desbrute_worker_args_t *worker_args = calloc(threads, sizeof(*worker_args));
|
|
if (tids == NULL || worker_args == NULL) {
|
|
free(tids);
|
|
free(worker_args);
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
mfulc_desbrute_shared_t shared = {0};
|
|
uint64_t start_ms = msclock();
|
|
uint32_t total = 1UL << 28;
|
|
uint32_t chunk = total / (uint32_t)threads;
|
|
uint32_t remainder = total % (uint32_t)threads;
|
|
uint32_t current = 0;
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("MFU DESBRUTE"));
|
|
PrintAndLogEx(INFO, "Mode....... " _YELLOW_("%s"), counterfeit_mode ? "counterfeit nonce" : "reader nonce");
|
|
PrintAndLogEx(INFO, "Segment.... " _YELLOW_("%d") " (key bytes " _YELLOW_("%d..%d") ")", segment, (segment - 1) * 4, ((segment - 1) * 4) + 3);
|
|
PrintAndLogEx(INFO, "Keyspace... " _YELLOW_("2^28") " = " _YELLOW_("%" PRIu32) " candidates", total);
|
|
PrintAndLogEx(INFO, "Threads.... " _YELLOW_("%d") " / " _YELLOW_("%d") " logical CPUs", threads, max_threads);
|
|
PrintAndLogEx(INFO, "Base key... " _GREEN_("%s"), sprint_hex_inrow(base_key, sizeof(base_key)));
|
|
if (counterfeit_mode) {
|
|
PrintAndLogEx(INFO, "Null ERndB. " _GREEN_("%s"), sprint_hex_inrow(init_ciphertext, sizeof(init_ciphertext)));
|
|
PrintAndLogEx(INFO, "Target..... " _GREEN_("%s"), sprint_hex_inrow(ciphertext, sizeof(ciphertext)));
|
|
PrintAndLogEx(INFO, "LFSR....... " _YELLOW_("%s"), lfsr_type == MFULC_DESBRUTE_LFSR_ULCG ? "ULCG" : "MFC (USCUID-UL/FJ8010)");
|
|
} else {
|
|
PrintAndLogEx(INFO, "ERndB...... " _GREEN_("%s"), sprint_hex_inrow(init_ciphertext, sizeof(init_ciphertext)));
|
|
PrintAndLogEx(INFO, "ERndA|B'... " _GREEN_("%s"), sprint_hex_inrow(tmp_blocks, sizeof(tmp_blocks)));
|
|
}
|
|
PrintAndLogEx(INFO, "Engine..... " _CYAN_("DES SP table + subkey contribution tables + vec candidate lanes"));
|
|
PrintAndLogEx(INFO, "Abort...... " _YELLOW_("press Enter"));
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
for (int i = 0; i < threads; i++) {
|
|
mfulc_desbrute_worker_args_t *wa = &worker_args[i];
|
|
|
|
wa->args.start = current;
|
|
wa->args.end = current + chunk + (i == threads - 1 ? remainder : 0);
|
|
wa->progress = wa->args.start;
|
|
wa->done = false;
|
|
wa->args.key_mode = segment - 1;
|
|
wa->args.candidate_in_k1 = wa->args.key_mode < 2;
|
|
wa->args.var_offset = wa->args.candidate_in_k1 ? ((wa->args.key_mode % 2) * 4) : (((wa->args.key_mode - 2) % 2) * 4);
|
|
wa->args.lfsr_type = lfsr_type;
|
|
wa->args.is_reader_mode = reader_mode;
|
|
wa->args.thread_id = i;
|
|
memcpy(wa->args.init_ciphertext, init_ciphertext, sizeof(init_ciphertext));
|
|
memcpy(wa->args.prev_ciphertext, prev_ciphertext, sizeof(prev_ciphertext));
|
|
memcpy(wa->args.ciphertext, ciphertext, sizeof(ciphertext));
|
|
memcpy(wa->args.base_key, base_key, sizeof(base_key));
|
|
wa->args.init_ip_block = mfulc_desbrute_perm(mfulc_desbrute_be64(init_ciphertext), 64, MFULC_DES_IP, 64);
|
|
wa->args.prev_ciphertext_be = mfulc_desbrute_be64(prev_ciphertext);
|
|
wa->args.ciphertext_ip_block = mfulc_desbrute_perm(mfulc_desbrute_be64(ciphertext), 64, MFULC_DES_IP, 64);
|
|
mfulc_desbrute_keyschedule(
|
|
mfulc_desbrute_be64(wa->args.candidate_in_k1 ? base_key + 8 : base_key),
|
|
wa->args.fixed_sk
|
|
);
|
|
mfulc_desbrute_compute_sk_tables(
|
|
wa->args.candidate_in_k1 ? base_key : base_key + 8,
|
|
wa->args.var_offset,
|
|
wa->args.cand_sk_base,
|
|
wa->args.cand_sk_contrib
|
|
);
|
|
wa->shared = &shared;
|
|
current = wa->args.end;
|
|
|
|
if (pthread_create(&tids[i], NULL, mfulc_desbrute_worker, wa) != 0) {
|
|
shared.aborted = true;
|
|
threads = i;
|
|
PrintAndLogEx(WARNING, "Failed creating worker thread");
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
uint64_t checked = 0;
|
|
bool all_done = true;
|
|
|
|
for (int i = 0; i < threads; i++) {
|
|
uint32_t p = worker_args[i].progress;
|
|
if (p < worker_args[i].args.start) {
|
|
p = worker_args[i].args.start;
|
|
}
|
|
if (p > worker_args[i].args.end) {
|
|
p = worker_args[i].args.end;
|
|
}
|
|
checked += (uint64_t)(p - worker_args[i].args.start);
|
|
if (worker_args[i].done == false) {
|
|
all_done = false;
|
|
}
|
|
}
|
|
|
|
uint64_t elapsed_now_ms = msclock() - start_ms;
|
|
double elapsed_s = elapsed_now_ms > 0 ? elapsed_now_ms / 1000.0 : 0.001;
|
|
double speed = checked / elapsed_s;
|
|
double pct = ((double)checked * 100.0) / (double)total;
|
|
uint64_t eta_s = 0;
|
|
char eta[16] = {0};
|
|
char elapsed[16] = {0};
|
|
char bar[32] = {0};
|
|
|
|
if (pct > 100.0) pct = 100.0;
|
|
if (speed > 0.0 && checked < total) {
|
|
eta_s = (uint64_t)(((double)total - (double)checked) / speed);
|
|
}
|
|
mfulc_desbrute_format_duration(eta_s, eta, sizeof(eta));
|
|
mfulc_desbrute_format_duration(elapsed_now_ms / 1000, elapsed, sizeof(elapsed));
|
|
mfulc_desbrute_progress_bar(pct, bar, sizeof(bar));
|
|
|
|
PrintAndLogEx(INPLACE, "%s%s" AEND " " _YELLOW_("%6.2f%%") " checked " _CYAN_("%" PRIu64) "/" _CYAN_("%" PRIu32) " " _GREEN_("%.0f keys/s") " elapsed " _YELLOW_("%s") " ETA " _YELLOW_("%s"),
|
|
mfulc_desbrute_progress_color(pct), bar, pct, checked, total, speed, elapsed, eta);
|
|
|
|
if (all_done || shared.found || shared.aborted) {
|
|
break;
|
|
}
|
|
if (kbd_enter_pressed()) {
|
|
shared.aborted = true;
|
|
break;
|
|
}
|
|
msleep(250);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
for (int i = 0; i < threads; i++) {
|
|
pthread_join(tids[i], NULL);
|
|
}
|
|
|
|
uint64_t elapsed_ms = msclock() - start_ms;
|
|
free(tids);
|
|
free(worker_args);
|
|
|
|
if (shared.aborted) {
|
|
PrintAndLogEx(WARNING, "Aborted");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
if (shared.found) {
|
|
PrintAndLogEx(SUCCESS, "Found key index: " _YELLOW_("%" PRIu32), shared.found_idx);
|
|
PrintAndLogEx(SUCCESS, "Full key: " _GREEN_("%s"), sprint_hex_inrow(shared.found_key, sizeof(shared.found_key)));
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), elapsed_ms / 1000.0);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
PrintAndLogEx(WARNING, "Key segment not found");
|
|
PrintAndLogEx(INFO, "Time spent " _YELLOW_("%.1fs"), elapsed_ms / 1000.0);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
/**
|
|
A test function to validate that the polarssl-function works the same
|
|
was as the openssl-implementation.
|
|
Commented out, since it requires openssl
|
|
|
|
static int CmdTestDES(const char * cmd)
|
|
{
|
|
uint8_t key[16] = {0x00};
|
|
|
|
memcpy(key,key3_3des_data,16);
|
|
DES_cblock RndA, RndB;
|
|
|
|
PrintAndLogEx(NORMAL, "----------OpenSSL DES implementation----------");
|
|
{
|
|
uint8_t e_RndB[8] = {0x00};
|
|
unsigned char RndARndB[16] = {0x00};
|
|
|
|
DES_cblock iv = { 0 };
|
|
DES_key_schedule ks1,ks2;
|
|
DES_cblock key1,key2;
|
|
|
|
memcpy(key,key3_3des_data,16);
|
|
memcpy(key1,key,8);
|
|
memcpy(key2,key+8,8);
|
|
|
|
|
|
DES_set_key((DES_cblock *)key1,&ks1);
|
|
DES_set_key((DES_cblock *)key2,&ks2);
|
|
|
|
DES_random_key(&RndA);
|
|
PrintAndLogEx(NORMAL, " RndA:%s",sprint_hex(RndA, 8));
|
|
PrintAndLogEx(NORMAL, " e_RndB:%s",sprint_hex(e_RndB, 8));
|
|
//void DES_ede2_cbc_encrypt(const unsigned char *input,
|
|
// unsigned char *output, long length, DES_key_schedule *ks1,
|
|
// DES_key_schedule *ks2, DES_cblock *ivec, int enc);
|
|
DES_ede2_cbc_encrypt(e_RndB,RndB,sizeof(e_RndB),&ks1,&ks2,&iv,0);
|
|
|
|
PrintAndLogEx(NORMAL, " RndB:%s",sprint_hex(RndB, 8));
|
|
rol(RndB,8);
|
|
memcpy(RndARndB,RndA,8);
|
|
memcpy(RndARndB+8,RndB,8);
|
|
PrintAndLogEx(NORMAL, " RA+B:%s",sprint_hex(RndARndB, 16));
|
|
DES_ede2_cbc_encrypt(RndARndB,RndARndB,sizeof(RndARndB),&ks1,&ks2,&e_RndB,1);
|
|
PrintAndLogEx(NORMAL, "enc(RA+B):%s",sprint_hex(RndARndB, 16));
|
|
|
|
}
|
|
PrintAndLogEx(NORMAL, "----------PolarSSL implementation----------");
|
|
{
|
|
uint8_t random_a[8] = { 0 };
|
|
uint8_t enc_random_a[8] = { 0 };
|
|
uint8_t random_b[8] = { 0 };
|
|
uint8_t enc_random_b[8] = { 0 };
|
|
uint8_t random_a_and_b[16] = { 0 };
|
|
des3_context ctx = { 0 };
|
|
|
|
memcpy(random_a, RndA,8);
|
|
|
|
uint8_t output[8] = { 0 };
|
|
uint8_t iv[8] = { 0 };
|
|
|
|
PrintAndLogEx(NORMAL, " RndA :%s",sprint_hex(random_a, 8));
|
|
PrintAndLogEx(NORMAL, " e_RndB:%s",sprint_hex(enc_random_b, 8));
|
|
|
|
des3_set2key_dec(&ctx, key);
|
|
|
|
des3_crypt_cbc(&ctx // des3_context *ctx
|
|
, DES_DECRYPT // int mode
|
|
, sizeof(random_b) // size_t length
|
|
, iv // unsigned char iv[8]
|
|
, enc_random_b // const unsigned char *input
|
|
, random_b // unsigned char *output
|
|
);
|
|
|
|
PrintAndLogEx(NORMAL, " RndB:%s",sprint_hex(random_b, 8));
|
|
|
|
rol(random_b,8);
|
|
memcpy(random_a_and_b ,random_a,8);
|
|
memcpy(random_a_and_b+8,random_b,8);
|
|
|
|
PrintAndLogEx(NORMAL, " RA+B:%s",sprint_hex(random_a_and_b, 16));
|
|
|
|
des3_set2key_enc(&ctx, key);
|
|
|
|
des3_crypt_cbc(&ctx // des3_context *ctx
|
|
, DES_ENCRYPT // int mode
|
|
, sizeof(random_a_and_b) // size_t length
|
|
, enc_random_b // unsigned char iv[8]
|
|
, random_a_and_b // const unsigned char *input
|
|
, random_a_and_b // unsigned char *output
|
|
);
|
|
|
|
PrintAndLogEx(NORMAL, "enc(RA+B):%s",sprint_hex(random_a_and_b, 16));
|
|
}
|
|
return 0;
|
|
}
|
|
**/
|
|
|
|
//
|
|
// Mifare Ultralight C/AES - Set keys
|
|
//
|
|
static int CmdHF14AMfUSetKey(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu setkey",
|
|
"Set the 3DES key on MIFARE Ultralight C tag and the AES keys on Ultralight AES. \n"
|
|
"Note: AUTH0 must allow unauthenticated writes to the key blocks\n"
|
|
"UL-AES:\n"
|
|
" New Key index 0... DataProtKey (default)\n"
|
|
" New Key index 1... UIDRetrKey\n",
|
|
"hf mfu setkey --key 49454D4B41455242214E4143554F5946\n"
|
|
"hf mfu setkey --key <16 hex bytes> --idx <0..1>"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "New key (16 hex bytes)"),
|
|
arg_int0("i", "idx", "<0..1>", "New key index (def: 0), only for UL-AES"),
|
|
arg_lit0("l", NULL, "Swap entered keys' endianness"),
|
|
arg_str0(NULL, "usekey", "<hex>", "Current UL-C 3DES or UL-AES DataProt key (16 hex bytes)"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have usekey"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
int key_index = arg_get_int_def(ctx, 2, 0);
|
|
bool swap_endian = arg_get_lit(ctx, 3);
|
|
int use_ak_len = 0;
|
|
uint8_t use_authenticationkey[16] = {0x00};
|
|
uint8_t *use_auth_key_ptr = use_authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 4, use_authenticationkey, &use_ak_len);
|
|
bool use_schann = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
if (ak_len != 16) {
|
|
PrintAndLogEx(WARNING, "Key must be 16 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (key_index < 0 || key_index > 1) {
|
|
PrintAndLogEx(WARNING, "Invalid key index");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool has_auth_key = false;
|
|
if (use_ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (use_ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "usekey must be 16 hex bytes\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with usekey");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
if ((tagtype & (MFU_TT_UL_C | MFU_TT_UL_AES)) == 0) {
|
|
PrintAndLogEx(WARNING, "Tag is not Ultralight C or Ultralight AES");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Swap endianness
|
|
// Beware, inverse condition! Key not used for authentication here
|
|
// if key not swapped by user, we need to swap it to write it in memory
|
|
if (swap_endian == false) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
}
|
|
|
|
// Swap endianness of usekey
|
|
if (swap_endian) {
|
|
if (use_ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
use_auth_key_ptr = SwapEndian64(use_authenticationkey, use_ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
use_auth_key_ptr = SwapEndian64(use_authenticationkey, use_ak_len, 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_auth_key) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
PrintAndLogEx(INFO, "Using 3des... " _GREEN_("%s"), sprint_hex_inrow(use_authenticationkey, use_ak_len));
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
PrintAndLogEx(INFO, "Using aes... " _GREEN_("%s"), sprint_hex_inrow(use_authenticationkey, use_ak_len));
|
|
}
|
|
}
|
|
|
|
mful_setkey_t packet = {
|
|
.has_auth_key = has_auth_key,
|
|
.use_schann = use_schann,
|
|
.key_index = key_index,
|
|
.keytype = ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) ? 1 : 3, // 1=ULC 3=ULAES
|
|
};
|
|
if (has_auth_key) {
|
|
memcpy(packet.auth_key, use_auth_key_ptr, 16);
|
|
}
|
|
memcpy(packet.key, auth_key_ptr, 16);
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
|
|
SendCommandNG(CMD_HF_MIFAREU_SETKEY, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_SETKEY, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "New key... " _GREEN_("%s"), sprint_hex_inrow(authenticationkey, sizeof(authenticationkey)));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed writing key");
|
|
return PM3_ESOFT;
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Magic UL / UL-C tags - Set UID
|
|
//
|
|
static int CmdHF14AMfUCSetUid(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu setuid",
|
|
"Set UID on MIFARE Ultralight tag.\n"
|
|
"This only works for `magic Ultralight` tags.",
|
|
"hf mfu setuid --uid 11223344556677"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("u", "uid", "<hex>", "New UID (7 hex bytes)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int u_len = 0;
|
|
uint8_t uid[7] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, uid, &u_len);
|
|
CLIParserFree(ctx);
|
|
|
|
if (u_len != 7) {
|
|
PrintAndLogEx(WARNING, "UID must be 7 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Please ignore possible transient BCC warnings");
|
|
mful_readblock_t packetr = {
|
|
.use_schann = false,
|
|
.keytype = 0,
|
|
.keylen = 0,
|
|
.num_of_blocks = 1,
|
|
};
|
|
mful_writeblock_t packetw = {
|
|
.keytype = 0,
|
|
.use_schann = false,
|
|
.keylen = 0,
|
|
};
|
|
|
|
// read block 2
|
|
packetr.block_no = 2;
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READBL, (uint8_t *)&packetr, sizeof(packetr));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_READBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "Command execute timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// save old block2.
|
|
uint8_t oldblock2[4] = {0x00};
|
|
memcpy(oldblock2, resp.data.asBytes, 4);
|
|
|
|
// Enforce bad BCC handling temporarily as BCC will be wrong between
|
|
// block 1 write and block2 write
|
|
hf14a_config_t config;
|
|
SendCommandNG(CMD_HF_ISO14443A_GET_CONFIG, NULL, 0);
|
|
if (WaitForResponseTimeout(CMD_HF_ISO14443A_GET_CONFIG, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execute timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
memcpy(&config, resp.data.asBytes, sizeof(hf14a_config_t));
|
|
int8_t oldconfig_bcc = config.forcebcc;
|
|
if (oldconfig_bcc != 2) {
|
|
config.forcebcc = 2;
|
|
SendCommandNG(CMD_HF_ISO14443A_SET_CONFIG, (uint8_t *)&config, sizeof(hf14a_config_t));
|
|
}
|
|
|
|
// block 0.
|
|
memcpy(packetw.data, uid, 3);
|
|
packetw.data[3] = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
|
|
packetw.block_no = 0;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "Command execute timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// block 1.
|
|
memcpy(packetw.data, uid + 3, 4);
|
|
packetw.block_no = 1;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "Command execute timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// block 2.
|
|
packetw.data[0] = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
|
|
memcpy(packetw.data + 1, oldblock2 + 1, 3);
|
|
packetw.block_no = 2;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "Command execute timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// restore BCC config
|
|
if (oldconfig_bcc != 2) {
|
|
config.forcebcc = oldconfig_bcc;
|
|
SendCommandNG(CMD_HF_ISO14443A_SET_CONFIG, (uint8_t *)&config, sizeof(hf14a_config_t));
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfUKeyGen(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu keygen",
|
|
"Calculate MFC keys based ",
|
|
"hf mfu keygen -r\n"
|
|
"hf mfu keygen --uid 11223344556677"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("u", "uid", "<hex>", "<4|7> hex byte UID"),
|
|
arg_lit0("r", NULL, "Read UID from tag"),
|
|
arg_u64_0("b", "blk", "<dec>", "Block number"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ulen = 0;
|
|
uint8_t uid[7];
|
|
CLIGetHexWithReturn(ctx, 1, uid, &ulen);
|
|
bool read_tag = arg_get_lit(ctx, 2);
|
|
uint8_t block = arg_get_u64_def(ctx, 3, 1) & 0xFF;
|
|
CLIParserFree(ctx);
|
|
|
|
if (read_tag) {
|
|
// read uid from tag
|
|
clearCommandBuffer();
|
|
SendIso14aReader(ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_RATS, NULL, 0);
|
|
PacketResponseNG resp;
|
|
uint8_t sel_6133 = 0;
|
|
if (WaitForIso14aReply(&resp, 2500, NULL, &sel_6133) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
uint64_t select_status = sel_6133;
|
|
// 0: couldn't read,
|
|
// 1: OK, with ATS
|
|
// 2: OK, no ATS
|
|
// 3: proprietary Anticollision
|
|
|
|
if (select_status == 0) {
|
|
PrintAndLogEx(WARNING, "iso14443a card select failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (card.uidlen != 4 && card.uidlen != 7) {
|
|
PrintAndLogEx(WARNING, "Wrong sized UID, expected 4|7 bytes got %d", card.uidlen);
|
|
return PM3_ESOFT;
|
|
}
|
|
ulen = card.uidlen;
|
|
memcpy(uid, card.uid, card.uidlen);
|
|
} else {
|
|
if (ulen != 4 && ulen != 7) {
|
|
PrintAndLogEx(ERR, "Must supply 4 or 7 hex byte uid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
uint8_t iv[8] = { 0x00 };
|
|
|
|
uint8_t mifarekeyA[] = { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5 };
|
|
uint8_t mifarekeyB[] = { 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5 };
|
|
uint8_t dkeyA[8] = { 0x00 };
|
|
uint8_t dkeyB[8] = { 0x00 };
|
|
|
|
uint8_t masterkey[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
|
|
uint8_t mix[8] = { 0x00 };
|
|
uint8_t divkey[8] = { 0x00 };
|
|
|
|
memcpy(mix, mifarekeyA, 4);
|
|
|
|
mix[4] = mifarekeyA[4] ^ uid[0];
|
|
mix[5] = mifarekeyA[5] ^ uid[1];
|
|
mix[6] = block ^ uid[2];
|
|
mix[7] = uid[3];
|
|
|
|
mbedtls_des3_context ctx_des3;
|
|
mbedtls_des3_set2key_enc(&ctx_des3, masterkey);
|
|
|
|
mbedtls_des3_crypt_cbc(&ctx_des3 // des3_context
|
|
, MBEDTLS_DES_ENCRYPT // int mode
|
|
, sizeof(mix) // length
|
|
, iv // iv[8]
|
|
, mix // input
|
|
, divkey // output
|
|
);
|
|
|
|
PrintAndLogEx(SUCCESS, "-- 3DES version");
|
|
PrintAndLogEx(SUCCESS, "Masterkey......... %s", sprint_hex(masterkey, sizeof(masterkey)));
|
|
PrintAndLogEx(SUCCESS, "UID............... %s", sprint_hex(uid, ulen));
|
|
PrintAndLogEx(SUCCESS, "block............. %0d", block);
|
|
PrintAndLogEx(SUCCESS, "Mifare key........ %s", sprint_hex(mifarekeyA, sizeof(mifarekeyA)));
|
|
PrintAndLogEx(SUCCESS, "Message........... %s", sprint_hex(mix, sizeof(mix)));
|
|
PrintAndLogEx(SUCCESS, "Diversified key... %s", sprint_hex(divkey + 1, 6));
|
|
|
|
for (int i = 0; i < ARRAYLEN(mifarekeyA); ++i) {
|
|
dkeyA[i] = (mifarekeyA[i] << 1) & 0xff;
|
|
dkeyA[6] |= ((mifarekeyA[i] >> 7) & 1) << (i + 1);
|
|
}
|
|
|
|
for (int i = 0; i < ARRAYLEN(mifarekeyB); ++i) {
|
|
dkeyB[1] |= ((mifarekeyB[i] >> 7) & 1) << (i + 1);
|
|
dkeyB[2 + i] = (mifarekeyB[i] << 1) & 0xff;
|
|
}
|
|
|
|
uint8_t zeros[8] = {0x00};
|
|
uint8_t newpwd[8] = {0x00};
|
|
uint8_t dmkey[24] = {0x00};
|
|
memcpy(dmkey, dkeyA, 8);
|
|
memcpy(dmkey + 8, dkeyB, 8);
|
|
memcpy(dmkey + 16, dkeyA, 8);
|
|
memset(iv, 0x00, 8);
|
|
|
|
mbedtls_des3_set3key_enc(&ctx_des3, dmkey);
|
|
|
|
mbedtls_des3_crypt_cbc(&ctx_des3 // des3_context
|
|
, MBEDTLS_DES_ENCRYPT // int mode
|
|
, sizeof(newpwd) // length
|
|
, iv // iv[8]
|
|
, zeros // input
|
|
, newpwd // output
|
|
);
|
|
|
|
PrintAndLogEx(SUCCESS, "\n-- DES version");
|
|
PrintAndLogEx(SUCCESS, "MIFARE dkeyA...... %s", sprint_hex(dkeyA, sizeof(dkeyA)));
|
|
PrintAndLogEx(SUCCESS, "MIFARE dkeyB...... %s", sprint_hex(dkeyB, sizeof(dkeyB)));
|
|
PrintAndLogEx(SUCCESS, "MIFARE ABA........ %s", sprint_hex(dmkey, sizeof(dmkey)));
|
|
PrintAndLogEx(SUCCESS, "MIFARE PWD........ %s", sprint_hex(newpwd, sizeof(newpwd)));
|
|
|
|
mbedtls_des3_free(&ctx_des3);
|
|
|
|
mbedtls_aes_context ctx_aes;
|
|
uint8_t aes_iv[16] = { 0x00 };
|
|
uint8_t aes_masterkey[] = { 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F };
|
|
uint8_t aes_input[16] = {0x01, 0x04, 0x2A, 0x2E, 0x19, 0x70, 0x1C, 0x80, 0x01, 0x04, 0x2A, 0x2E, 0x19, 0x70, 0x1C, 0x80};
|
|
uint8_t aes_output[16] = {0x00};
|
|
mbedtls_aes_setkey_enc(&ctx_aes, aes_masterkey, 128);
|
|
mbedtls_aes_crypt_cbc(&ctx_aes, MBEDTLS_AES_ENCRYPT, 16, aes_iv, aes_input, aes_output);
|
|
mbedtls_aes_free(&ctx_aes);
|
|
|
|
PrintAndLogEx(SUCCESS, "\n-- AES version");
|
|
PrintAndLogEx(SUCCESS, "MIFARE AES mk..... %s", sprint_hex(aes_masterkey, sizeof(aes_masterkey)));
|
|
PrintAndLogEx(SUCCESS, "MIFARE Div........ %s", sprint_hex(aes_output, sizeof(aes_output)));
|
|
|
|
// next. from the diversify_key method.
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfUPwdGen(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu pwdgen",
|
|
"Generate different passwords from known pwdgen algos",
|
|
"hf mfu pwdgen -r\n"
|
|
"hf mfu pwdgen --uid 11223344556677\n"
|
|
"hf mfu pwdgen --test"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("u", "uid", "<hex>", "UID (7 hex bytes)"),
|
|
arg_lit0("r", NULL, "Read UID from tag"),
|
|
arg_lit0(NULL, "test", "self test"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int u_len = 0;
|
|
uint8_t uid[7] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, uid, &u_len);
|
|
bool use_tag = arg_get_lit(ctx, 2);
|
|
bool selftest = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
if (selftest) {
|
|
return generator_selftest();
|
|
}
|
|
|
|
uint8_t philips_mfg[10] = {0};
|
|
|
|
if (use_tag) {
|
|
// read uid from tag
|
|
int res = ul_read_uid(uid);
|
|
if (res == PM3_ELENGTH) {
|
|
// got 4 byte UID, lets adapt to 7 bytes :)
|
|
memset(uid + 4, 0x00, 3);
|
|
u_len = 7;
|
|
} else {
|
|
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
if (ul_select(&card)) {
|
|
// Philips toothbrush needs page 0x21-0x23
|
|
uint8_t data[16] = {0x00};
|
|
int status = ul_read(0x21, data, sizeof(data), false);
|
|
if (status <= 0) {
|
|
PrintAndLogEx(DEBUG, "Error: tag didn't answer to READ");
|
|
} else if (status == 16) {
|
|
memcpy(philips_mfg, data + 2, sizeof(philips_mfg));
|
|
}
|
|
DropField();
|
|
}
|
|
}
|
|
} else {
|
|
if (u_len != 7 && u_len != 4) {
|
|
PrintAndLogEx(WARNING, "Key must be 7 hex bytes");
|
|
return PM3_EINVARG;
|
|
} else if (u_len == 4) {
|
|
// adapt to 7 bytes :)
|
|
memset(uid + 4, 0x00, 3);
|
|
u_len = 7;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "-----------------------------------");
|
|
PrintAndLogEx(INFO, " UID 4b... " _YELLOW_("%s"), sprint_hex(uid, 4));
|
|
PrintAndLogEx(INFO, " UID 7b... " _YELLOW_("%s"), sprint_hex(uid, 7));
|
|
PrintAndLogEx(INFO, "-----------------------------------");
|
|
PrintAndLogEx(INFO, " algo pwd pack");
|
|
PrintAndLogEx(INFO, "-----------------------------+-----");
|
|
PrintAndLogEx(INFO, " Transport EV1..... %08X | %04X", ul_ev1_pwdgenA(uid), ul_ev1_packgenA(uid));
|
|
PrintAndLogEx(INFO, " Amiibo............ %08X | %04X", ul_ev1_pwdgenB(uid), ul_ev1_packgenB(uid));
|
|
PrintAndLogEx(INFO, " Lego Dimension.... %08X | %04X", ul_ev1_pwdgenC(uid), ul_ev1_packgenC(uid));
|
|
PrintAndLogEx(INFO, " XYZ 3D printer.... %08X | %04X", ul_ev1_pwdgenD(uid), ul_ev1_packgenD(uid));
|
|
PrintAndLogEx(INFO, " Xiaomi purifier... %08X | %04X", ul_ev1_pwdgenE(uid), ul_ev1_packgenE(uid));
|
|
PrintAndLogEx(INFO, " NTAG tools........ %08X | %04X", ul_ev1_pwdgenF(uid), ul_ev1_packgen_def(uid));
|
|
if (philips_mfg[0] != 0) {
|
|
PrintAndLogEx(INFO, " Philips Toothbrush | %08X | %04X", ul_ev1_pwdgenG(uid, philips_mfg), ul_ev1_packgenG(uid, philips_mfg));
|
|
}
|
|
PrintAndLogEx(INFO, "-----------------------------+-----");
|
|
PrintAndLogEx(INFO, _CYAN_("Vingcard"));
|
|
uint64_t key = 0;
|
|
mfc_algo_saflok_one(uid, 0, 0, &key);
|
|
PrintAndLogEx(INFO, " Saflok algo | %012" PRIX64, key);
|
|
PrintAndLogEx(INFO, " SALTO algo");
|
|
PrintAndLogEx(INFO, " Dorma Kaba algo");
|
|
PrintAndLogEx(INFO, " STiD algo");
|
|
PrintAndLogEx(INFO, "-------------------------------------");
|
|
key = 0;
|
|
mfc_algo_bambu_one(uid, 0, MF_KEY_A, &key);
|
|
PrintAndLogEx(INFO, " Bambu........ %012" PRIX64, key);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// MFU TearOff against OTP
|
|
// Moebius et al
|
|
//
|
|
static int CmdHF14AMfuOtpTearoff(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu otptear",
|
|
"Tear-off test against OTP block",
|
|
"hf mfu otptear -b 3\n"
|
|
"hf mfu otptear -b 3 -i 100 -s 1000\n"
|
|
"hf mfu otptear -b 3 -i 1 -e 200\n"
|
|
"hf mfu otptear -b 3 -i 100 -s 200 -e 2500 -d FFFFFFFF -t EEEEEEEE\n"
|
|
"hf mfu otptear -b 3 -i 100 -s 200 -e 2500 -d FFFFFFFF -t EEEEEEEE -m 00000000 -> quit when OTP is reset"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_u64_0("b", "blk", "<dec>", "target block (def 8)"),
|
|
arg_u64_0("i", "inc", "<dec>", "increase time steps (def 500 us)"),
|
|
arg_u64_0("e", "end", "<dec>", "end time (def 3000 us)"),
|
|
arg_u64_0("s", "start", "<dec>", "start time (def 0 us)"),
|
|
arg_str0("d", "data", "<hex>", "initialise data before run (4 bytes)"),
|
|
arg_str0("t", "test", "<hex>", "test write data (4 bytes, 00000000 by default)"),
|
|
arg_str0("m", "match", "<hex>", "exit criteria, if block matches this value (4 bytes)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint8_t blockno = arg_get_u32_def(ctx, 1, 8);
|
|
uint32_t steps = arg_get_u32_def(ctx, 2, 500);
|
|
uint32_t end = arg_get_u32_def(ctx, 3, 3000);
|
|
uint32_t start = arg_get_u32_def(ctx, 4, 0);
|
|
|
|
int d_len = 0;
|
|
uint8_t data[4] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 5, data, &d_len);
|
|
bool use_data = (d_len > 0);
|
|
|
|
int t_len = 0;
|
|
uint8_t test[4] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 6, test, &t_len);
|
|
|
|
int m_len = 0;
|
|
uint8_t match[4] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 7, match, &m_len);
|
|
bool use_match = (m_len > 0);
|
|
CLIParserFree(ctx);
|
|
|
|
if (blockno < 2) {
|
|
PrintAndLogEx(WARNING, "Block number must be larger than 2.");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (end < steps) {
|
|
PrintAndLogEx(WARNING, "end time smaller than increase value");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (end > 65535) {
|
|
PrintAndLogEx(WARNING, "end time - out of 1 .. 65535 range");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (start > (end - steps)) {
|
|
PrintAndLogEx(WARNING, "Start time larger than (end time + steps)");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (d_len && d_len != 4) {
|
|
PrintAndLogEx(WARNING, "data must be 4 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (t_len && t_len != 4) {
|
|
PrintAndLogEx(WARNING, "test data must be 4 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (m_len && m_len != 4) {
|
|
PrintAndLogEx(WARNING, "match data must be 4 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t teardata[4] = {0x00};
|
|
memcpy(teardata, test, sizeof(test));
|
|
|
|
PrintAndLogEx(INFO, "----------------- " _CYAN_("MFU Tear off") " ---------------------");
|
|
PrintAndLogEx(INFO, "Starting Tear-off test");
|
|
PrintAndLogEx(INFO, "Target block no: %u", blockno);
|
|
if (use_data) {
|
|
PrintAndLogEx(INFO, "Target initial block data : %s", sprint_hex_inrow(data, 4));
|
|
}
|
|
PrintAndLogEx(INFO, "Target write block data : %s", sprint_hex_inrow(teardata, 4));
|
|
if (use_match) {
|
|
PrintAndLogEx(INFO, "Target match block data : %s", sprint_hex_inrow(match, 4));
|
|
}
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
|
|
bool lock_on = false;
|
|
uint8_t pre[4] = {0};
|
|
uint8_t post[4] = {0};
|
|
uint32_t current = start;
|
|
int phase_begin_clear = -1;
|
|
int phase_end_clear = -1;
|
|
int phase_begin_newwr = -1;
|
|
int phase_end_newwr = -1;
|
|
bool skip_phase1 = false;
|
|
uint8_t retries = 0;
|
|
uint8_t error_retries = 0;
|
|
|
|
// read block X
|
|
mful_readblock_t packetr = {
|
|
.use_schann = false,
|
|
.keytype = 0,
|
|
.keylen = 0,
|
|
.num_of_blocks = 1,
|
|
};
|
|
|
|
mful_writeblock_t packetw = {
|
|
.block_no = blockno,
|
|
.keytype = 0,
|
|
.use_schann = false,
|
|
.keylen = 0,
|
|
};
|
|
memcpy(packetw.data, data, sizeof(data));
|
|
|
|
while ((current <= (end - steps)) && (error_retries < 10)) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
|
|
break;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Using tear-off delay " _GREEN_("%" PRIu32) " us", current);
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
|
|
if (use_data) {
|
|
SendCommandNG(CMD_HF_MIFAREU_WRITEBL, (uint8_t *)&packetw, sizeof(packetw));
|
|
if ((WaitForResponseTimeout(CMD_HF_MIFAREU_WRITEBL, &resp, 1500) == false) ||
|
|
(resp.status != PM3_SUCCESS)) {
|
|
PrintAndLogEx(FAILED, "Failed to write block BEFORE");
|
|
error_retries++;
|
|
continue; // try again
|
|
}
|
|
}
|
|
|
|
packetr.block_no = blockno;
|
|
SendCommandNG(CMD_HF_MIFAREU_READBL, (uint8_t *)&packetr, sizeof(packetr));
|
|
if ((WaitForResponseTimeout(CMD_HF_MIFAREU_READBL, &resp, 1500) == false) ||
|
|
(resp.status != PM3_SUCCESS)) {
|
|
PrintAndLogEx(FAILED, "Failed to read block BEFORE");
|
|
error_retries++;
|
|
continue; // try again
|
|
}
|
|
memcpy(post, resp.data.asBytes, sizeof(post));
|
|
|
|
clearCommandBuffer();
|
|
uint8_t tbuf[sizeof(mfu_otp_tearoff_t) + sizeof(teardata)] = {0};
|
|
mfu_otp_tearoff_t *tpayload = (mfu_otp_tearoff_t *)tbuf;
|
|
tpayload->blockno = blockno;
|
|
tpayload->tearoff_time = current;
|
|
memcpy(tpayload->data, teardata, sizeof(teardata));
|
|
SendCommandNG(CMD_HF_MFU_OTP_TEAROFF, tbuf, sizeof(tbuf));
|
|
|
|
// we be getting ACK that we are silently ignoring here..
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_MFU_OTP_TEAROFF, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "Failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Tear off reporting failure to select tag");
|
|
error_retries++;
|
|
continue;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_READBL, (uint8_t *)&packetr, sizeof(packetr));
|
|
if ((WaitForResponseTimeout(CMD_HF_MIFAREU_READBL, &resp, 1500) == false) ||
|
|
(resp.status != PM3_SUCCESS)) {
|
|
PrintAndLogEx(FAILED, "Failed to read block BEFORE");
|
|
error_retries++;
|
|
continue; // try again
|
|
}
|
|
memcpy(post, resp.data.asBytes, sizeof(post));
|
|
|
|
error_retries = 0;
|
|
char prestr[20] = {0};
|
|
snprintf(prestr, sizeof(prestr), "%s", sprint_hex_inrow(pre, sizeof(pre)));
|
|
char poststr[20] = {0};
|
|
snprintf(poststr, sizeof(poststr), "%s", sprint_hex_inrow(post, sizeof(post)));
|
|
|
|
if (memcmp(pre, post, sizeof(pre)) == 0) {
|
|
|
|
PrintAndLogEx(INFO, "Current : %02d (0x%02X) %s"
|
|
, blockno
|
|
, blockno
|
|
, poststr
|
|
);
|
|
} else {
|
|
PrintAndLogEx(INFO, _CYAN_("Tear off occurred") " : %02d (0x%02X) %s => " _RED_("%s")
|
|
, blockno
|
|
, blockno
|
|
, prestr
|
|
, poststr
|
|
);
|
|
|
|
lock_on = true;
|
|
|
|
uint32_t post32 = bytes_to_num(post, 4);
|
|
uint32_t pre32 = bytes_to_num(pre, 4);
|
|
|
|
if ((phase_begin_clear == -1) && (bitcount32(pre32) > bitcount32(post32))) {
|
|
phase_begin_clear = current;
|
|
}
|
|
|
|
if ((phase_begin_clear > -1) && (phase_end_clear == -1) && (bitcount32(post32) == 0)) {
|
|
phase_end_clear = current;
|
|
}
|
|
|
|
if ((current == start) && (phase_end_clear > -1)) {
|
|
skip_phase1 = true;
|
|
}
|
|
// new write phase must be atleast 100us later..
|
|
if (((bitcount32(pre32) == 0) || (phase_end_clear > -1)) && (phase_begin_newwr == -1) && (bitcount32(post32) != 0) && (skip_phase1 || (current > (phase_end_clear + 100)))) {
|
|
phase_begin_newwr = current;
|
|
}
|
|
|
|
if ((phase_begin_newwr > -1) && (phase_end_newwr == -1) && (memcmp(post, teardata, sizeof(teardata)) == 0)) {
|
|
phase_end_newwr = current;
|
|
}
|
|
}
|
|
|
|
if (use_match && memcmp(post, match, sizeof(post)) == 0) {
|
|
PrintAndLogEx(SUCCESS, "Block matches stop condition!\n");
|
|
break;
|
|
}
|
|
|
|
/* TEMPORALLY DISABLED
|
|
uint8_t d0, d1, d2, d3;
|
|
d0 = *resp.data.asBytes;
|
|
d1 = *(resp.data.asBytes + 1);
|
|
d2 = *(resp.data.asBytes + 2);
|
|
d3 = *(resp.data.asBytes + 3);
|
|
if ((d0 != 0xFF) || (d1 != 0xFF) || (d2 != 0xFF) || (d3 = ! 0xFF)) {
|
|
PrintAndLogEx(NORMAL, "---------------------------------");
|
|
PrintAndLogEx(NORMAL, " EFFECT AT: %d us", actualTime);
|
|
PrintAndLogEx(NORMAL, "---------------------------------\n");
|
|
}
|
|
*/
|
|
if (start != end) {
|
|
current += steps;
|
|
} else {
|
|
if (lock_on == false) {
|
|
if (++retries == 20) {
|
|
current++;
|
|
end++;
|
|
start++;
|
|
PrintAndLogEx(INFO, _CYAN_("Retried %u times, increased delay with 1us"), retries);
|
|
retries = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
if ((phase_begin_clear > - 1) && (phase_begin_clear != start)) {
|
|
PrintAndLogEx(INFO, "Erase phase start boundary around " _YELLOW_("%5d") " us", phase_begin_clear);
|
|
}
|
|
|
|
if ((phase_end_clear > - 1) && (phase_end_clear != start)) {
|
|
PrintAndLogEx(INFO, "Erase phase end boundary around " _YELLOW_("%5d") " us", phase_end_clear);
|
|
}
|
|
|
|
if (phase_begin_newwr > - 1) {
|
|
PrintAndLogEx(INFO, "Write phase start boundary around " _YELLOW_("%5d") " us", phase_begin_newwr);
|
|
}
|
|
|
|
if (phase_end_newwr > - 1) {
|
|
PrintAndLogEx(INFO, "Write phase end boundary around " _YELLOW_("%5d") " us", phase_end_newwr);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static int counter_reset_tear(iso14a_card_select_t *card, uint8_t cnt_no) {
|
|
|
|
PrintAndLogEx(INFO, "Reset tear check");
|
|
|
|
uint8_t cw[6] = { MIFARE_ULEV1_INCR_CNT, cnt_no, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t ct[1] = {0};
|
|
uint8_t resp[10] = {0};
|
|
|
|
if (ul_select(card) == false) {
|
|
PrintAndLogEx(FAILED, "failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
if (ul_send_cmd_raw(cw, sizeof(cw), resp, sizeof(resp), false) < 0) {
|
|
PrintAndLogEx(FAILED, "failed to write all ZEROS");
|
|
return PM3_ESOFT;
|
|
}
|
|
if (ulev1_readTearing(cnt_no, ct, sizeof(ct)) < 0) {
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
if (ct[0] != 0xBD) {
|
|
PrintAndLogEx(INFO, "Resetting seem to have failed, WHY!?");
|
|
return PM3_ESOFT;
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static int CmdHF14AMfuEv1CounterTearoff(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu countertear",
|
|
"Tear-off test against a Ev1 counter",
|
|
"hf mfu countertear -c 0 -> target counter 0\n"
|
|
"hf mfu countertear -c 0 -s 200 -> target counter 0, start delay 200\n"
|
|
"hf mfu countertear -c 0 -x 020000 -> target counter 0, increasing the counter by 2 bytes\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0("c", "cnt", "<0,1,2>", "Target this EV1 counter (0,1,2)"),
|
|
arg_int0("i", "inc", "<dec>", "time interval to increase in each iteration - default 10 us"),
|
|
arg_int0("l", "limit", "<dec>", "test upper limit time - default 3000 us"),
|
|
arg_int0("s", "start", "<dec>", "test start time - default 500 us"),
|
|
arg_int0(NULL, "fix", "<dec>", "test fixed loop delay"),
|
|
arg_str0("x", "hex", NULL, "3 byte hex to increase counter with - default 010000"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int interval = 0;
|
|
int time_limit, start_time = 0;
|
|
int counter = arg_get_int_def(ctx, 1, 0);
|
|
int fixed = arg_get_int_def(ctx, 5, -1);
|
|
|
|
if (fixed == -1) {
|
|
interval = arg_get_int_def(ctx, 2, 10);
|
|
time_limit = arg_get_int_def(ctx, 3, 3000);
|
|
start_time = arg_get_int_def(ctx, 4, 500);
|
|
} else {
|
|
start_time = fixed;
|
|
interval = 0;
|
|
time_limit = fixed;
|
|
}
|
|
|
|
uint8_t newvalue[5] = {0};
|
|
int newvaluelen = 0;
|
|
CLIGetHexWithReturn(ctx, 6, newvalue, &newvaluelen);
|
|
CLIParserFree(ctx);
|
|
|
|
// Validations
|
|
if (start_time > (time_limit - interval)) {
|
|
PrintAndLogEx(WARNING, "Wrong start time number");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (time_limit < interval) {
|
|
PrintAndLogEx(WARNING, "Wrong time limit number");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (time_limit > 65535) {
|
|
PrintAndLogEx(WARNING, "You can't set delay out of 1..65535 range!");
|
|
return PM3_EINVARG;
|
|
}
|
|
uint8_t cnt_no = 0;
|
|
if (counter < 0 || counter > 2) {
|
|
PrintAndLogEx(WARNING, "Counter must 0, 1 or 2");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
cnt_no = (uint8_t)counter;
|
|
|
|
iso14a_card_select_t card;
|
|
|
|
// reset counter tear
|
|
counter_reset_tear(&card, cnt_no);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(INFO, "failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t initial_cnt[3] = {0, 0, 0};
|
|
int len = ulev1_readCounter(cnt_no, initial_cnt, sizeof(initial_cnt), false);
|
|
if (len != sizeof(initial_cnt)) {
|
|
PrintAndLogEx(WARNING, "failed to read counter");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t initial_tear[1] = {0};
|
|
len = ulev1_readTearing(cnt_no, initial_tear, sizeof(initial_tear));
|
|
DropField();
|
|
if (len != sizeof(initial_tear)) {
|
|
PrintAndLogEx(WARNING, "failed to read ANTITEAR, exiting... %d", len);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "------------- " _CYAN_("MFU Ev1 Counter Tear off") " -------------");
|
|
PrintAndLogEx(INFO, "Target counter no [ " _GREEN_("%u") " ]", counter);
|
|
PrintAndLogEx(INFO, "counter value [ " _GREEN_("%s") " ]", sprint_hex_inrow(initial_cnt, sizeof(initial_cnt)));
|
|
PrintAndLogEx(INFO, "anti-tear value [ " _GREEN_("%02X") " ]", initial_tear[0]);
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
|
|
uint8_t post_tear = 0;
|
|
uint8_t pre[3] = {0};
|
|
uint8_t post[3] = {0};
|
|
uint32_t a = 0, b = 0;
|
|
uint32_t loop = 0;
|
|
|
|
uint8_t cntresp[3] = {0, 0, 0};
|
|
uint8_t tear[1] = {0};
|
|
int tlen = 0;
|
|
int delay_bd;
|
|
int delay_00;
|
|
char prestr[20];
|
|
char poststr[20];
|
|
int const_post = 0;
|
|
bool post_tear_check;
|
|
|
|
counter_reset_tear(&card, cnt_no);
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "BEFORE, failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
msleep(30);
|
|
|
|
if (fixed == -1) {
|
|
for (delay_bd = start_time; delay_bd <= time_limit; delay_bd += interval) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "\nAborted via keyboard!\n");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
msleep(30);
|
|
|
|
memset(cntresp, 0, sizeof(cntresp));
|
|
int rlen = ulev1_readCounter(cnt_no, cntresp, sizeof(cntresp), false);
|
|
if (rlen == sizeof(cntresp)) {
|
|
memcpy(pre, cntresp, sizeof(pre));
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to read COUNTER, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
struct p {
|
|
uint8_t counter;
|
|
uint32_t tearoff_time;
|
|
uint8_t value[3];
|
|
} PACKED payload;
|
|
payload.counter = cnt_no;
|
|
payload.tearoff_time = delay_bd;
|
|
memcpy(payload.value, (uint8_t[]) {0x01, 0x00, 0x00}, sizeof(payload.value));
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_MFU_COUNTER_TEAROFF, (uint8_t *)&payload, sizeof(payload));
|
|
if (WaitForResponseTimeout(CMD_HF_MFU_COUNTER_TEAROFF, &resp, 2000) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\nTear off command failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
DropField();
|
|
msleep(50);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
msleep(30);
|
|
|
|
memset(cntresp, 0, sizeof(cntresp));
|
|
rlen = ulev1_readCounter(cnt_no, cntresp, sizeof(cntresp), false);
|
|
if (rlen == sizeof(cntresp)) {
|
|
memcpy(post, cntresp, sizeof(post));
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read COUNTER, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
tear[0] = 0;
|
|
tlen = ulev1_readTearing(cnt_no, tear, sizeof(tear));
|
|
if (tlen == sizeof(tear)) {
|
|
post_tear = tear[0];
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
snprintf(poststr, sizeof(poststr), "%s", sprint_hex_inrow(post, sizeof(post)));
|
|
post_tear_check = (post_tear == 0xBD);
|
|
a = (pre[0] | pre[1] << 8 | pre[2] << 16);
|
|
b = (post[0] | post[1] << 8 | post[2] << 16);
|
|
PrintAndLogEx(INPLACE, "Delay: " _YELLOW_("%d") " Tear: %s Counter: " _YELLOW_("%s") ""
|
|
, delay_bd
|
|
, sprint_hex_inrow(tear, sizeof(tear))
|
|
, poststr
|
|
|
|
);
|
|
|
|
DropField();
|
|
msleep(50);
|
|
|
|
if (b != a && post_tear == 0xBD) {
|
|
if (b < a && b == 0) {
|
|
counter_reset_tear(&card, cnt_no);
|
|
counter_reset_tear(&card, cnt_no);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to select card, looping...");
|
|
continue;
|
|
}
|
|
msleep(30);
|
|
|
|
tear[0] = 0;
|
|
tlen = ulev1_readTearing(cnt_no, tear, sizeof(tear));
|
|
if (tlen == sizeof(tear)) {
|
|
post_tear = tear[0];
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
continue;
|
|
}
|
|
post_tear_check = (post_tear == 0xBD);
|
|
PrintAndLogEx(INFO, "------------------- " _GREEN_("ZEROS value!") " -------------------");
|
|
|
|
PrintAndLogEx(SUCCESS, "Attempt: " _YELLOW_("%d"), loop);
|
|
PrintAndLogEx(SUCCESS, "Delay BD: " _YELLOW_("%d"), delay_bd);
|
|
PrintAndLogEx(SUCCESS, "Counter: %s -> " _GREEN_("%s"), prestr, poststr);
|
|
PrintAndLogEx(SUCCESS, "Tear status: 0x%02X ( %s )",
|
|
post_tear,
|
|
post_tear_check ? _GREEN_("OK") : _RED_("NOT OK"));
|
|
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
const_post = b;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "BD delay found: " _GREEN_("%d"), delay_bd);
|
|
DropField();
|
|
break;
|
|
}
|
|
|
|
}
|
|
} else if (fixed != -1) delay_bd = fixed;
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "BEFORE, failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
msleep(30);
|
|
|
|
for (delay_00 = 100; delay_00 <= 2000; delay_00 += 10) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "\nAborted via keyboard!\n");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
struct p {
|
|
uint8_t counter;
|
|
uint32_t tearoff_time;
|
|
uint8_t value[3];
|
|
} PACKED payload;
|
|
payload.counter = cnt_no;
|
|
payload.tearoff_time = delay_00;
|
|
memcpy(payload.value, (uint8_t[]) {0x00, 0x00, 0x00}, sizeof(payload.value));
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_MFU_COUNTER_TEAROFF, (uint8_t *)&payload, sizeof(payload));
|
|
if (WaitForResponseTimeout(CMD_HF_MFU_COUNTER_TEAROFF, &resp, 2000) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\nTear off command failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
DropField();
|
|
msleep(50);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
msleep(30);
|
|
|
|
|
|
tear[0] = 0;
|
|
tlen = ulev1_readTearing(cnt_no, tear, sizeof(tear));
|
|
if (tlen == sizeof(tear)) {
|
|
post_tear = tear[0];
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
post_tear_check = (post_tear == 0xBD);
|
|
|
|
PrintAndLogEx(INPLACE, "Delay: " _YELLOW_("%d") " Tear: %s Counter: " _YELLOW_("%s") ""
|
|
, delay_00
|
|
, sprint_hex_inrow(tear, sizeof(tear))
|
|
, poststr
|
|
);
|
|
if (post_tear == 0x00) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "00 delay found: " _GREEN_("%d"), delay_00);
|
|
DropField();
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
counter_reset_tear(&card, cnt_no);
|
|
counter_reset_tear(&card, cnt_no);
|
|
|
|
while (true) {
|
|
|
|
loop++;
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "\nAborted via keyboard!\n");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to select card, looping...");
|
|
continue;
|
|
}
|
|
msleep(30);
|
|
|
|
memset(cntresp, 0, sizeof(cntresp));
|
|
int rlen = ulev1_readCounter(cnt_no, cntresp, sizeof(cntresp), false);
|
|
if (rlen == sizeof(cntresp)) {
|
|
memcpy(pre, cntresp, sizeof(pre));
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to read COUNTER, exiting...");
|
|
continue;
|
|
}
|
|
|
|
struct p {
|
|
uint8_t counter;
|
|
uint32_t tearoff_time;
|
|
uint8_t value[3];
|
|
} PACKED payload;
|
|
payload.counter = cnt_no;
|
|
payload.tearoff_time = delay_bd;
|
|
if (newvaluelen > 0) {
|
|
memcpy(payload.value, newvalue, sizeof(payload.value));
|
|
} else {
|
|
memcpy(payload.value, (uint8_t[]) {0x01, 0x00, 0x00}, sizeof(payload.value));
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_MFU_COUNTER_TEAROFF, (uint8_t *)&payload, sizeof(payload));
|
|
if (WaitForResponseTimeout(CMD_HF_MFU_COUNTER_TEAROFF, &resp, 2000) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\nTear off command failed");
|
|
continue;
|
|
}
|
|
|
|
DropField();
|
|
msleep(50);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to select card, exiting...");
|
|
continue;
|
|
}
|
|
msleep(30);
|
|
|
|
payload.counter = cnt_no;
|
|
payload.tearoff_time = delay_00;
|
|
memcpy(payload.value, (uint8_t[]) {0x00, 0x00, 0x00}, sizeof(payload.value));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MFU_COUNTER_TEAROFF, (uint8_t *)&payload, sizeof(payload));
|
|
if (WaitForResponseTimeout(CMD_HF_MFU_COUNTER_TEAROFF, &resp, 2000) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "\nTear off command failed");
|
|
continue;
|
|
}
|
|
|
|
DropField();
|
|
msleep(50);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to select card, looping...");
|
|
continue;
|
|
}
|
|
msleep(30);
|
|
|
|
memset(cntresp, 0, sizeof(cntresp));
|
|
rlen = ulev1_readCounter(cnt_no, cntresp, sizeof(cntresp), false);
|
|
if (rlen == sizeof(cntresp)) {
|
|
memcpy(post, cntresp, sizeof(post));
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read COUNTER, exiting...");
|
|
continue;
|
|
}
|
|
|
|
tear[0] = 0;
|
|
tlen = ulev1_readTearing(cnt_no, tear, sizeof(tear));
|
|
if (tlen == sizeof(tear)) {
|
|
post_tear = tear[0];
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
continue;
|
|
}
|
|
|
|
snprintf(prestr, sizeof(prestr), "%s", sprint_hex_inrow(pre, sizeof(pre)));
|
|
|
|
snprintf(poststr, sizeof(poststr), "%s", sprint_hex_inrow(post, sizeof(post)));
|
|
|
|
post_tear_check = (post_tear == 0xBD);
|
|
a = (pre[0] | pre[1] << 8 | pre[2] << 16);
|
|
b = (post[0] | post[1] << 8 | post[2] << 16);
|
|
|
|
// A != B
|
|
if (memcmp(pre, post, sizeof(pre)) != 0) {
|
|
|
|
if (b < a) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
if (b == 0) {
|
|
counter_reset_tear(&card, cnt_no);
|
|
counter_reset_tear(&card, cnt_no);
|
|
|
|
if (ul_select(&card) == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "BEFORE, failed to select card, looping...");
|
|
continue;
|
|
}
|
|
msleep(30);
|
|
|
|
tear[0] = 0;
|
|
tlen = ulev1_readTearing(cnt_no, tear, sizeof(tear));
|
|
if (tlen == sizeof(tear)) {
|
|
post_tear = tear[0];
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(FAILED, "AFTER, failed to read ANTITEAR, exiting...");
|
|
continue;
|
|
}
|
|
post_tear_check = (post_tear == 0xBD);
|
|
|
|
PrintAndLogEx(INFO, "------------------- " _GREEN_("ZEROS value!") " -------------------");
|
|
|
|
PrintAndLogEx(SUCCESS, "Attempt: " _YELLOW_("%d"), loop);
|
|
PrintAndLogEx(SUCCESS, "Delay BD/00: " _YELLOW_("%d/%d"), delay_bd, delay_00);
|
|
PrintAndLogEx(SUCCESS, "Counter: %s -> " _GREEN_("%s"), prestr, poststr);
|
|
PrintAndLogEx(SUCCESS, "Tear status: 0x%02X ( %s )",
|
|
post_tear,
|
|
post_tear_check ? _GREEN_("OK") : _RED_("NOT OK"));
|
|
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
break;
|
|
}
|
|
|
|
else {
|
|
PrintAndLogEx(INFO, "----------------------- " _GREEN_("LESS") " -----------------------");
|
|
|
|
PrintAndLogEx(SUCCESS, "Attempt: " _YELLOW_("%d"), loop);
|
|
PrintAndLogEx(SUCCESS, "Delay BD/00: " _YELLOW_("%d/%d"), delay_bd, delay_00);
|
|
PrintAndLogEx(SUCCESS, "Counter: %s -> " _GREEN_("%s"), prestr, poststr);
|
|
PrintAndLogEx(SUCCESS, "Tear status: 0x%02X ( %s )",
|
|
post_tear,
|
|
post_tear_check ? _RED_("NOT OK") : _GREEN_("OK"));
|
|
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "\r" _YELLOW_("[ %d ]") " Delay BD/00: " _YELLOW_("%d/%d") " Counter: %s -> %s Tear: 0x%02X ( %s )" NOLF
|
|
, loop
|
|
, delay_bd
|
|
, delay_00
|
|
, prestr
|
|
, poststr
|
|
, post_tear
|
|
, post_tear_check ? _RED_("NOT OK") : _GREEN_("OK")
|
|
);
|
|
|
|
if (loop % 20 == 0 && const_post == b && delay_bd != time_limit && fixed == -1) {
|
|
delay_bd += interval;
|
|
const_post = b;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "BD delay increased " _GREEN_("%d"), delay_bd);
|
|
} else if (loop % 20 == 0 && b - const_post > 10 && delay_bd != start_time && fixed == -1) {
|
|
delay_bd -= interval;
|
|
const_post = b;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "BD delay reduced " _RED_("%d"), delay_bd);
|
|
}
|
|
if (loop % 20 == 0) const_post = b;
|
|
|
|
if (loop % 5 == 0 && post_tear_check) {
|
|
delay_00 += 5;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "00 delay increased " _GREEN_("%d"), delay_00);
|
|
}
|
|
}
|
|
|
|
DropField();
|
|
msleep(50);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// name, identifying bytes, decode function, hints text
|
|
// identifying bits
|
|
// 1. getversion data must match.
|
|
// 2. magic bytes in the readable payload
|
|
|
|
|
|
int CmdHF14MfuNDEFRead(const char *Cmd) {
|
|
|
|
int ak_len;
|
|
int status;
|
|
uint16_t ndef_size = 0;
|
|
bool has_auth_key = false;
|
|
bool swap_endian = false;
|
|
|
|
iso14a_card_select_t card;
|
|
uint8_t data[16] = {0x00};
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
uint8_t pack[4] = {0, 0, 0, 0};
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu ndefread",
|
|
"Prints NFC Data Exchange Format (NDEF)",
|
|
"hf mfu ndefread -> shows NDEF data\n"
|
|
"hf mfu ndefread -k ffffffff -> shows NDEF data with key\n"
|
|
"hf mfu ndefread -f myfilename -> save raw NDEF to file"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "Replace default key for NDEF", NULL),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_str0("f", "file", "<fn>", "Save raw NDEF to file"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
swap_endian = arg_get_lit(ctx, 2);
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
bool verbose = arg_get_lit(ctx, 4);
|
|
bool use_schann = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
switch (ak_len) {
|
|
case 0:
|
|
break;
|
|
case 4:
|
|
case 16:
|
|
has_auth_key = true;
|
|
break;
|
|
default:
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Get tag type
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
PrintAndLogEx(WARNING, "No Ultralight / NTAG based tag found");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Is tag UL/NTAG?
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
// Select and Auth
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) return PM3_ESOFT;
|
|
|
|
// read pages 0,1,2,3 (should read 4pages)
|
|
status = ul_read(0, data, sizeof(data), use_schann);
|
|
if (status <= 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (status != 16) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag returned %d bytes, need 16 to read the NDEF Container", status);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (ndef_print_CC(data + 12) == PM3_ESOFT) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didn't contain a NDEF Container");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// size of the NDEF data area
|
|
ndef_size = ndef_get_maxsize(data + 12);
|
|
if (ndef_size == 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag announces an empty NDEF data area");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// The data area starts at block 4, so cap the announced size to what the
|
|
// identified tag can physically hold. UL_MEMORY_ARRAY holds the last valid
|
|
// block number, hence the +1.
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
|
|
int avail = (UL_MEMORY_ARRAY[idx] + 1 - MFU_NDEF_FIRST_BLOCK) * MFU_BLOCK_SIZE;
|
|
if (avail > 0 && ndef_size > avail) {
|
|
PrintAndLogEx(INFO, "NDEF data area (%u bytes) is larger than the tag (%d bytes), using tag size"
|
|
, ndef_size
|
|
, avail
|
|
);
|
|
ndef_size = avail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// MLEN can announce up to 2040 bytes but the READ command addresses blocks
|
|
// with a single byte, so block 255 is the last one we can ask for.
|
|
if (ndef_size > MFU_NDEF_MAX_BYTES) {
|
|
PrintAndLogEx(INFO, "NDEF data area (%u bytes) exceeds the addressable range, using %d bytes"
|
|
, ndef_size
|
|
, MFU_NDEF_MAX_BYTES
|
|
);
|
|
ndef_size = MFU_NDEF_MAX_BYTES;
|
|
}
|
|
|
|
// The following read returns 4 blocks (16 bytes) at a time,
|
|
// round the buffer up to a multiple of 16.
|
|
uint16_t readsize = (ndef_size + 15) & ~15U;
|
|
|
|
// allocate mem, one extra byte so the buffer is always NUL terminated
|
|
uint8_t *records = calloc(readsize + 1, sizeof(uint8_t));
|
|
if (records == NULL) {
|
|
DropField();
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
// read NDEF records.
|
|
for (uint16_t i = 0, j = 0; i < readsize; i += 16, j += 4) {
|
|
status = ul_read(MFU_NDEF_FIRST_BLOCK + j, records + i, 16, use_schann);
|
|
if (status <= 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didn't answer to READ");
|
|
free(records);
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
DropField();
|
|
|
|
status = NDEFRecordsDecodeAndPrint(records, (size_t)ndef_size, verbose);
|
|
if (status != PM3_SUCCESS) {
|
|
status = NDEFDecodeAndPrint(records, (size_t)ndef_size, verbose);
|
|
}
|
|
|
|
// get total NDEF length before save. If fails, we save it all
|
|
size_t n = 0;
|
|
if (NDEFGetTotalLength(records, ndef_size, &n) != PM3_SUCCESS)
|
|
n = ndef_size;
|
|
|
|
pm3_save_dump(filename, records, n, jsfNDEF);
|
|
|
|
|
|
char *jooki = strstr((char *)records, "s.jooki.rocks/s/?s=");
|
|
if (jooki) {
|
|
jooki += 17;
|
|
while (jooki) {
|
|
if ((*jooki) != '=')
|
|
jooki++;
|
|
else {
|
|
jooki++;
|
|
char s[17] = {0};
|
|
strncpy(s, jooki, 16);
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf jooki decode -d %s") "` to decode", s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
char *mattel = strstr((char *)records, ".pid.mattel/");
|
|
if (mattel) {
|
|
mattel += 12;
|
|
while (mattel) {
|
|
if ((*mattel) != '/')
|
|
mattel++;
|
|
else {
|
|
mattel++;
|
|
char b64[33] = {0};
|
|
strncpy(b64, mattel, 32);
|
|
uint8_t arr[24] = {0};
|
|
size_t arrlen = 0;
|
|
mbedtls_base64_decode(arr, sizeof(arr), &arrlen, (const unsigned char *)b64, 32);
|
|
|
|
PrintAndLogEx(INFO, "decoded... %s", sprint_hex(arr, arrlen));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(records);
|
|
return status;
|
|
}
|
|
|
|
|
|
// Build a NDEF message from the CLI options and write it into the data area of a
|
|
// MIFARE Ultralight / NTAG tag. The tag has to be NDEF formatted already, this
|
|
// command never touches the Capability Container in block 3.
|
|
int CmdHF14MfuNDEFWrite(const char *Cmd) {
|
|
|
|
int ak_len = 0;
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
bool swap_endian = false;
|
|
|
|
iso14a_card_select_t card;
|
|
uint8_t data[16] = {0x00};
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
uint8_t pack[4] = {0, 0, 0, 0};
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu ndefwrite",
|
|
"Write NFC Data Exchange Format (NDEF) records to a MIFARE Ultralight / NTAG tag.\n"
|
|
"The tag must already be NDEF formatted, ie carry a Capability Container in block 3.\n"
|
|
"\n"
|
|
"Combine several of --uri, --text and --aar to build a multi record message,\n"
|
|
"records are added in that order. Alternatively supply raw NDEF bytes with -d\n"
|
|
"or -f, those get wrapped in a TLV container automatically when they are not\n"
|
|
"already. Use `nfc encode` to build such raw bytes offline.\n"
|
|
"\n"
|
|
"Note: the tag is re-selected and re-authenticated for every block written,\n"
|
|
"so a large message takes a while.",
|
|
"hf mfu ndefwrite --uri https://proxmark.com\n"
|
|
"hf mfu ndefwrite --uri tel:+123456789\n"
|
|
"hf mfu ndefwrite --text \"hello world\"\n"
|
|
"hf mfu ndefwrite --aar com.example.app\n"
|
|
"hf mfu ndefwrite --uri https://proxmark.com --aar com.example.app\n"
|
|
"hf mfu ndefwrite --uri https://proxmark.com -k ffffffff\n"
|
|
"hf mfu ndefwrite -d 0311D1010D550270726F786D61726B2E636F6DFE\n"
|
|
"hf mfu ndefwrite -f myfilename"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key endianness"),
|
|
arg_str0(NULL, "uri", "<str>", "URI record. URL, tel:, mailto:, ..."),
|
|
arg_str0(NULL, "text", "<str>", "Text record"),
|
|
arg_str0(NULL, "lang", "<str>", "language code for the text record (default: en)"),
|
|
arg_str0(NULL, "aar", "<str>", "Android Application Record, ie an app package name"),
|
|
arg_str0("d", "data", "<hex>", "Raw NDEF bytes to write"),
|
|
arg_str0("f", "file", "<fn>", "Raw NDEF file to write"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
swap_endian = arg_get_lit(ctx, 2);
|
|
|
|
// CLIParamStrToBuf copies the trailing NUL as well but only rejects lengths
|
|
// strictly above maxdatalen, so leave room for that byte
|
|
int urilen = 0;
|
|
char uri[1024] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)uri, sizeof(uri) - 1, &urilen);
|
|
|
|
int textlen = 0;
|
|
char text[1024] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)text, sizeof(text) - 1, &textlen);
|
|
|
|
int langlen = 0;
|
|
char lang[32] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)lang, sizeof(lang) - 1, &langlen);
|
|
|
|
int aarlen = 0;
|
|
char aar[256] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)aar, sizeof(aar) - 1, &aarlen);
|
|
|
|
int rawlen = 0;
|
|
uint8_t raw[MFU_NDEF_MAX_BYTES] = {0};
|
|
CLIGetHexWithReturn(ctx, 7, raw, &rawlen);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
bool use_schann = arg_get_lit(ctx, 10);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_records = (urilen || textlen || aarlen);
|
|
int sources = (has_records ? 1 : 0) + (rawlen ? 1 : 0) + (fnlen ? 1 : 0);
|
|
if (sources == 0) {
|
|
PrintAndLogEx(ERR, "Nothing to write. See `" _YELLOW_("hf mfu ndefwrite -h") "`");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (sources > 1) {
|
|
PrintAndLogEx(ERR, "Use either the record options, -d or -f, not a mix of them");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if ((langlen != 0) && (textlen == 0)) {
|
|
PrintAndLogEx(WARNING, "--lang only applies to a text record, ignoring");
|
|
}
|
|
|
|
switch (ak_len) {
|
|
case 0:
|
|
break;
|
|
case 4:
|
|
has_pwd = true;
|
|
break;
|
|
case 16:
|
|
has_auth_key = true;
|
|
break;
|
|
default:
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && (has_auth_key == false)) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// build the TLV block that goes onto the tag
|
|
// ------------------------------------------------------------------
|
|
uint8_t tlv[MFU_NDEF_MAX_BYTES] = {0};
|
|
size_t tlv_len = 0;
|
|
int res = PM3_SUCCESS;
|
|
|
|
if (has_records) {
|
|
|
|
NDEFRecordDesc_t recs[3] = {{0}};
|
|
size_t count = 0;
|
|
|
|
uint8_t p_uri[sizeof(uri) + 1] = {0};
|
|
size_t p_uri_len = 0;
|
|
if (urilen) {
|
|
res = NDEFEncodePayloadURI(uri, p_uri, sizeof(p_uri), &p_uri_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to encode URI record");
|
|
return res;
|
|
}
|
|
recs[count].tnf = tnfWellKnownRecord;
|
|
recs[count].type = (const uint8_t *)NDEF_TYPE_URI;
|
|
recs[count].typeLen = strlen(NDEF_TYPE_URI);
|
|
recs[count].payload = p_uri;
|
|
recs[count].payloadLen = p_uri_len;
|
|
count++;
|
|
}
|
|
|
|
uint8_t p_text[sizeof(text) + sizeof(lang) + 1] = {0};
|
|
size_t p_text_len = 0;
|
|
if (textlen) {
|
|
res = NDEFEncodePayloadText(text, lang, p_text, sizeof(p_text), &p_text_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to encode Text record");
|
|
return res;
|
|
}
|
|
recs[count].tnf = tnfWellKnownRecord;
|
|
recs[count].type = (const uint8_t *)NDEF_TYPE_TEXT;
|
|
recs[count].typeLen = strlen(NDEF_TYPE_TEXT);
|
|
recs[count].payload = p_text;
|
|
recs[count].payloadLen = p_text_len;
|
|
count++;
|
|
}
|
|
|
|
uint8_t p_aar[sizeof(aar)] = {0};
|
|
size_t p_aar_len = 0;
|
|
if (aarlen) {
|
|
res = NDEFEncodePayloadAAR(aar, p_aar, sizeof(p_aar), &p_aar_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to encode Android Application Record");
|
|
return res;
|
|
}
|
|
recs[count].tnf = tnfExternalRecord;
|
|
recs[count].type = (const uint8_t *)NDEF_ANDROID_AAR;
|
|
recs[count].typeLen = strlen(NDEF_ANDROID_AAR);
|
|
recs[count].payload = p_aar;
|
|
recs[count].payloadLen = p_aar_len;
|
|
count++;
|
|
}
|
|
|
|
uint8_t msg[MFU_NDEF_MAX_BYTES] = {0};
|
|
size_t msg_len = 0;
|
|
res = NDEFEncodeMessage(recs, count, msg, sizeof(msg), &msg_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to encode NDEF message");
|
|
return res;
|
|
}
|
|
|
|
res = NDEFEncodeTLV(msg, msg_len, tlv, sizeof(tlv), &tlv_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
|
|
return res;
|
|
}
|
|
|
|
} else {
|
|
|
|
uint8_t *src = raw;
|
|
size_t src_len = (size_t)rawlen;
|
|
uint8_t *dump = NULL;
|
|
|
|
if (fnlen) {
|
|
size_t bytes_read = 0;
|
|
res = pm3_load_dump(filename, (void **)&dump, &bytes_read, sizeof(raw));
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
src = dump;
|
|
src_len = bytes_read;
|
|
}
|
|
|
|
if (src_len == 0) {
|
|
PrintAndLogEx(ERR, "No NDEF data supplied");
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// is it a TLV block already?
|
|
bool is_tlv = false;
|
|
switch (src[0]) {
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0xFD:
|
|
case 0xFE:
|
|
is_tlv = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (is_tlv) {
|
|
if (src_len > sizeof(tlv)) {
|
|
PrintAndLogEx(ERR, "NDEF data too large, %zu bytes", src_len);
|
|
free(dump);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(tlv, src, src_len);
|
|
tlv_len = src_len;
|
|
} else {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Raw data is not TLV wrapped, adding container");
|
|
}
|
|
res = NDEFEncodeTLV(src, src_len, tlv, sizeof(tlv), &tlv_len);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
|
|
free(dump);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
free(dump);
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "TLV block [%zu]...", tlv_len);
|
|
print_hex_noascii_break(tlv, tlv_len, 32);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// find the tag and check the message fits
|
|
// ------------------------------------------------------------------
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
PrintAndLogEx(WARNING, "No Ultralight / NTAG based tag found");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
ul_print_type(tagtype, 0);
|
|
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// read blocks 0..3 to get at the Capability Container
|
|
int status = ul_read(0, data, sizeof(data), use_schann);
|
|
if (status <= 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didnt answer to READ");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (status != 16) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag returned %d bytes, need 16 to read the NDEF Container", status);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (ndef_print_CC(data + 12) == PM3_ESOFT) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag didnt contain a NDEF Container");
|
|
PrintAndLogEx(HINT, "Hint: the tag needs to be NDEF formatted first");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// size of the NDEF data area
|
|
uint16_t ndef_size = ndef_get_maxsize(data + 12);
|
|
if (ndef_size == 0) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Error: tag announces an empty NDEF data area");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// The data area starts at block 4, so cap the announced size to what the
|
|
// identified tag can physically hold. UL_MEMORY_ARRAY holds the last valid
|
|
// block number, hence the +1.
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
|
|
int avail = (UL_MEMORY_ARRAY[idx] + 1 - MFU_NDEF_FIRST_BLOCK) * MFU_BLOCK_SIZE;
|
|
if (avail > 0 && ndef_size > avail) {
|
|
PrintAndLogEx(INFO, "NDEF data area (%u bytes) is larger than the tag (%d bytes), using tag size"
|
|
, ndef_size
|
|
, avail
|
|
);
|
|
ndef_size = avail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// MLEN can announce up to 2040 bytes but WRITE addresses blocks with a single
|
|
// byte, so block 255 is the last one we can reach.
|
|
if (ndef_size > MFU_NDEF_MAX_BYTES) {
|
|
ndef_size = MFU_NDEF_MAX_BYTES;
|
|
}
|
|
|
|
if (tlv_len > ndef_size) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "NDEF message is too large for this tag");
|
|
PrintAndLogEx(ERR, " message..... " _RED_("%zu") " bytes", tlv_len);
|
|
PrintAndLogEx(ERR, " tag holds... " _GREEN_("%u") " bytes", ndef_size);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
DropField();
|
|
|
|
// ------------------------------------------------------------------
|
|
// write it out, one block at a time
|
|
// ------------------------------------------------------------------
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
keytype = 1; // UL_C auth
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
keytype = 3; // UL_AES auth
|
|
} else {
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
}
|
|
|
|
// pad the tail so the last block is complete
|
|
size_t padded_len = (tlv_len + (MFU_BLOCK_SIZE - 1)) & ~((size_t)MFU_BLOCK_SIZE - 1);
|
|
uint16_t blocks = (uint16_t)(padded_len / MFU_BLOCK_SIZE);
|
|
|
|
PrintAndLogEx(INFO, "Writing " _YELLOW_("%u") " blocks ( %zu bytes ) from block " _YELLOW_("%d")
|
|
, blocks
|
|
, padded_len
|
|
, MFU_NDEF_FIRST_BLOCK
|
|
);
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
|
|
for (uint16_t i = 0; i < blocks; i++) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "aborted via keyboard!");
|
|
if (i > 0) {
|
|
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
|
|
, MFU_NDEF_FIRST_BLOCK
|
|
, MFU_NDEF_FIRST_BLOCK + i - 1
|
|
);
|
|
}
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
uint8_t blockno = (uint8_t)(MFU_NDEF_FIRST_BLOCK + i);
|
|
|
|
res = mfu_write_block(tlv + (i * MFU_BLOCK_SIZE), MFU_BLOCK_SIZE, keytype, auth_key_ptr, blockno, use_schann);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// a protected tag simply stops answering, so the write times out
|
|
// rather than coming back with an explicit error
|
|
if (res == PM3_ETIMEOUT) {
|
|
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("timeout") " )", blockno);
|
|
} else {
|
|
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("fail") " )", blockno);
|
|
}
|
|
PrintAndLogEx(HINT, "Hint: Check password / key!");
|
|
|
|
if (i > 0) {
|
|
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
|
|
, MFU_NDEF_FIRST_BLOCK
|
|
, blockno - 1
|
|
);
|
|
}
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(INPLACE, "Block %u / %u", i + 1, blocks);
|
|
}
|
|
|
|
DropField();
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to verify");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// NDEF formatting - restores the NXP factory delivery content (Capability
|
|
// Container + empty NDEF message) for the detected tag type. Block 3 is OTP.
|
|
// Per-type sources and rationale: doc/mfu_ndef_format_notes.md
|
|
typedef struct {
|
|
uint64_t tagtype;
|
|
const char *name;
|
|
uint8_t page[3][MFU_BLOCK_SIZE]; // pages 03h, 04h, 05h
|
|
} mfu_ndef_format_t;
|
|
|
|
static const mfu_ndef_format_t mfu_ndef_format_table[] = {
|
|
{ MFU_TT_UL, "MIFARE Ultralight", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_UL_C, "MIFARE Ultralight C", {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_UL_EV1_48, "MIFARE Ultralight EV1 48", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_UL_EV1_128, "MIFARE Ultralight EV1 128", {{0xE1, 0x10, 0x10, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_203, "NTAG203", {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_210, "NTAG210", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_210u, "NTAG210u", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_212, "NTAG212", {{0xE1, 0x10, 0x10, 0x00}, {0x01, 0x03, 0x90, 0x0A}, {0x34, 0x03, 0x00, 0xFE}} },
|
|
{ MFU_TT_NTAG_213, "NTAG213", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
|
|
{ MFU_TT_NTAG_213_F, "NTAG213F", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
|
|
{ MFU_TT_NTAG_213_TT, "NTAG213TT", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
|
|
{ MFU_TT_NTAG_213_C, "NTAG213C", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
|
|
{ MFU_TT_NTAG_215, "NTAG215", {{0xE1, 0x10, 0x3E, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_216, "NTAG216", {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
{ MFU_TT_NTAG_216_F, "NTAG216F", {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
|
|
};
|
|
|
|
static const mfu_ndef_format_t *mfu_get_ndef_format(uint64_t tagtype) {
|
|
for (size_t i = 0; i < ARRAYLEN(mfu_ndef_format_table); i++) {
|
|
uint64_t tt = mfu_ndef_format_table[i].tagtype;
|
|
if ((tagtype & tt) == tt) {
|
|
return &mfu_ndef_format_table[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int CmdHF14MfuNDEFFormat(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu ndefformat",
|
|
"Format a MIFARE Ultralight / NTAG tag for NDEF by writing the Capability\n"
|
|
"Container to block 3, followed by an empty NDEF message.\n"
|
|
"\n"
|
|
"Writes NXP factory delivery content for the detected tag type. Block 3 is\n"
|
|
"One Time Programmable; unknown types and unreachable CCs are refused.\n"
|
|
"\n"
|
|
"Note: the tag is re-selected and re-authenticated for each block written.",
|
|
"hf mfu ndefformat\n"
|
|
"hf mfu ndefformat -v\n"
|
|
"hf mfu ndefformat --erase\n"
|
|
"hf mfu ndefformat -k FFFFFFFF\n"
|
|
"hf mfu ndefformat -k 49454D4B41455242214E4143554F5946\n"
|
|
"hf mfu ndefformat -d E1101200 --force"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key endianness"),
|
|
arg_str0("d", "data", "<hex>", "Capability Container to write, 4 bytes. Overrides the detected type"),
|
|
arg_lit0(NULL, "erase", "Also zero the rest of the NDEF data area"),
|
|
arg_lit0(NULL, "force", "Continue on an unknown tag type, or with an oversized -d value"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
|
|
int cc_len = 0;
|
|
uint8_t cc_override[MFU_BLOCK_SIZE] = {0x00};
|
|
CLIGetHexWithReturn(ctx, 3, cc_override, &cc_len);
|
|
|
|
bool erase = arg_get_lit(ctx, 4);
|
|
bool force = arg_get_lit(ctx, 5);
|
|
bool verbose = arg_get_lit(ctx, 6);
|
|
bool use_schann = arg_get_lit(ctx, 7);
|
|
CLIParserFree(ctx);
|
|
|
|
if ((cc_len != 0) && (cc_len != MFU_BLOCK_SIZE)) {
|
|
PrintAndLogEx(WARNING, "Capability Container must be %d bytes, got %d", MFU_BLOCK_SIZE, cc_len);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
switch (ak_len) {
|
|
case 0:
|
|
break;
|
|
case 4:
|
|
has_pwd = true;
|
|
break;
|
|
case 16:
|
|
has_auth_key = true;
|
|
break;
|
|
default:
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && (has_auth_key == false)) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
PrintAndLogEx(WARNING, "No Ultralight / NTAG based tag found");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
ul_print_type(tagtype, 0);
|
|
|
|
const mfu_ndef_format_t *fmt = mfu_get_ndef_format(tagtype);
|
|
if (fmt == NULL) {
|
|
if (cc_len == 0) {
|
|
PrintAndLogEx(FAILED, "Don't know the Capability Container for this tag type");
|
|
PrintAndLogEx(INFO, "Block 3 is One Time Programmable, a wrong value can not be undone,");
|
|
PrintAndLogEx(INFO, "so this command will not guess one.");
|
|
PrintAndLogEx(HINT, "Hint: supply it yourself with `" _YELLOW_("hf mfu ndefformat -d <hex> --force") "`");
|
|
return PM3_ENOTIMPL;
|
|
}
|
|
if (force == false) {
|
|
PrintAndLogEx(FAILED, "Unknown tag type, add `" _YELLOW_("--force") "` to write anyway");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (erase) {
|
|
PrintAndLogEx(FAILED, "Refusing to erase on an unknown tag type");
|
|
PrintAndLogEx(INFO, "The end of the data area can not be established, so the erase");
|
|
PrintAndLogEx(INFO, "could run into the lock bytes, configuration or key pages.");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
// pages 03h, 04h and 05h as they will be written
|
|
uint8_t pages[3][MFU_BLOCK_SIZE] = {{0}};
|
|
if (fmt != NULL) {
|
|
memcpy(pages, fmt->page, sizeof(pages));
|
|
} else {
|
|
// unknown type, -d plus --force: user supplied CC and an empty NDEF message
|
|
const uint8_t empty[2][MFU_BLOCK_SIZE] = {{0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}};
|
|
memcpy(pages[1], empty[0], MFU_BLOCK_SIZE);
|
|
memcpy(pages[2], empty[1], MFU_BLOCK_SIZE);
|
|
}
|
|
|
|
if (cc_len == MFU_BLOCK_SIZE) {
|
|
|
|
// -d must not announce more memory than this tag type actually has
|
|
if ((fmt != NULL) && (cc_override[2] > fmt->page[0][2]) && (force == false)) {
|
|
PrintAndLogEx(FAILED, "Capability Container announces more memory than this tag has");
|
|
PrintAndLogEx(INFO, " requested... %d bytes ( MLEN %02X )", cc_override[2] * 8, cc_override[2]);
|
|
PrintAndLogEx(INFO, " tag holds... %d bytes ( MLEN %02X )", fmt->page[0][2] * 8, fmt->page[0][2]);
|
|
PrintAndLogEx(INFO, "Block 3 is One Time Programmable, this can not be undone.");
|
|
PrintAndLogEx(HINT, "Hint: add `" _YELLOW_("--force") "` if you really mean it");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
memcpy(pages[0], cc_override, MFU_BLOCK_SIZE);
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Tag type... " _YELLOW_("%s"), (fmt != NULL) ? fmt->name : "unknown");
|
|
for (uint8_t i = 0; i < 3; i++) {
|
|
PrintAndLogEx(INFO, "Block %2u... %s"
|
|
, MFU_NDEF_CC_BLOCK + i
|
|
, sprint_hex_inrow(pages[i], MFU_BLOCK_SIZE)
|
|
);
|
|
}
|
|
}
|
|
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
uint8_t pack[4] = {0, 0, 0, 0};
|
|
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// read blocks 0..3 so the current Capability Container can be inspected
|
|
uint8_t data[16] = {0x00};
|
|
int status = ul_read(0, data, sizeof(data), use_schann);
|
|
DropField();
|
|
|
|
if (status <= 0) {
|
|
PrintAndLogEx(ERR, "Error: tag didnt answer to READ");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (status != 16) {
|
|
PrintAndLogEx(ERR, "Error: tag returned %d bytes, need 16 to read the NDEF Container", status);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// block 3 is OTP: refuse a target the current content can't reach via OR
|
|
uint8_t *cur = data + (MFU_NDEF_CC_BLOCK * MFU_BLOCK_SIZE);
|
|
bool blank = true;
|
|
bool reachable = true;
|
|
for (uint8_t i = 0; i < MFU_BLOCK_SIZE; i++) {
|
|
if (cur[i] != 0x00) {
|
|
blank = false;
|
|
}
|
|
if ((cur[i] | pages[0][i]) != pages[0][i]) {
|
|
reachable = false;
|
|
}
|
|
}
|
|
|
|
if (reachable == false) {
|
|
PrintAndLogEx(FAILED, "Capability Container can not be written on this tag");
|
|
PrintAndLogEx(INFO, " on tag now... " _RED_("%s"), sprint_hex_inrow(cur, MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(INFO, " wanted....... " _GREEN_("%s"), sprint_hex_inrow(pages[0], MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(INFO, "Block 3 is One Time Programmable. A write is OR'ed with the current");
|
|
PrintAndLogEx(INFO, "content, so a bit that is already 1 can not be cleared again.");
|
|
ndef_print_CC(cur);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (blank == false) {
|
|
PrintAndLogEx(WARNING, "Tag already carries a Capability Container ( " _YELLOW_("%s") " )"
|
|
, sprint_hex_inrow(cur, MFU_BLOCK_SIZE)
|
|
);
|
|
if (verbose) {
|
|
ndef_print_CC(cur);
|
|
}
|
|
}
|
|
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
keytype = 1; // UL_C auth
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
keytype = 3; // UL_AES auth
|
|
} else {
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
}
|
|
|
|
// erase range from the table MLEN, never from -d - can't run past user memory
|
|
uint16_t last_block = MFU_NDEF_CC_BLOCK + 2;
|
|
if (erase && (fmt != NULL)) {
|
|
last_block = (uint16_t)(MFU_NDEF_CC_BLOCK + (fmt->page[0][2] * 2));
|
|
}
|
|
|
|
// block 255 is the last one WRITE can reach with its single address byte
|
|
if (last_block > 0xFF) {
|
|
PrintAndLogEx(INFO, "Data area runs past block 255, stopping at the last addressable block");
|
|
last_block = 0xFF;
|
|
}
|
|
|
|
uint16_t total = (uint16_t)(last_block - MFU_NDEF_CC_BLOCK + 1);
|
|
|
|
PrintAndLogEx(INFO, "Writing " _YELLOW_("%u") " blocks from block " _YELLOW_("%d"), total, MFU_NDEF_CC_BLOCK);
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
|
|
uint16_t done = 0;
|
|
const uint8_t zeros[MFU_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00};
|
|
|
|
for (uint16_t blockno = MFU_NDEF_CC_BLOCK; blockno <= last_block; blockno++) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "aborted via keyboard!");
|
|
if (done > 0) {
|
|
PrintAndLogEx(WARNING, "tag holds a partially written NDEF data area, blocks %d..%u"
|
|
, MFU_NDEF_CC_BLOCK
|
|
, blockno - 1
|
|
);
|
|
}
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
const uint8_t *src = zeros;
|
|
if (blockno < MFU_NDEF_CC_BLOCK + 3) {
|
|
src = pages[blockno - MFU_NDEF_CC_BLOCK];
|
|
}
|
|
|
|
int res = mfu_write_block(src, MFU_BLOCK_SIZE, keytype, auth_key_ptr, (uint8_t)blockno, use_schann);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// a protected tag simply stops answering, so the write times out
|
|
// rather than coming back with an explicit error
|
|
if (res == PM3_ETIMEOUT) {
|
|
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("timeout") " )", blockno);
|
|
} else {
|
|
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("fail") " )", blockno);
|
|
}
|
|
PrintAndLogEx(HINT, "Hint: Check password / key!");
|
|
|
|
if (done > 0) {
|
|
PrintAndLogEx(WARNING, "tag holds a partially written NDEF data area, blocks %d..%u"
|
|
, MFU_NDEF_CC_BLOCK
|
|
, blockno - 1
|
|
);
|
|
}
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
done++;
|
|
PrintAndLogEx(INPLACE, "Block %u / %u", done, total);
|
|
}
|
|
|
|
DropField();
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// read the formatted blocks back rather than trust the write status alone
|
|
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
PrintAndLogEx(WARNING, "Wrote the tag but could not re-select it to verify");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to check it yourself");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t verify[16] = {0x00};
|
|
status = ul_read(MFU_NDEF_CC_BLOCK, verify, sizeof(verify), use_schann);
|
|
DropField();
|
|
|
|
if (status != 16) {
|
|
PrintAndLogEx(WARNING, "Wrote the tag but could not read it back to verify");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to check it yourself");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// the read started at block 3, so the three formatted blocks are at offset 0
|
|
if (memcmp(verify, pages[0], MFU_BLOCK_SIZE) != 0) {
|
|
PrintAndLogEx(FAILED, "Capability Container did not take");
|
|
PrintAndLogEx(INFO, " wanted...... " _GREEN_("%s"), sprint_hex_inrow(pages[0], MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(INFO, " on tag now.. " _RED_("%s"), sprint_hex_inrow(verify, MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(HINT, "Hint: block 3 is OTP, check whether its block locking bit is set");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (memcmp(verify + MFU_BLOCK_SIZE, pages[1], 2 * MFU_BLOCK_SIZE) != 0) {
|
|
PrintAndLogEx(FAILED, "Capability Container is correct but the empty NDEF message is not");
|
|
// one sprint_hex_inrow() call per line: it returns a shared static buffer
|
|
PrintAndLogEx(INFO, " wanted...... " _GREEN_("%s"), sprint_hex_inrow(pages[1], 2 * MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(INFO, " on tag now.. " _RED_("%s"), sprint_hex_inrow(verify + MFU_BLOCK_SIZE, 2 * MFU_BLOCK_SIZE));
|
|
PrintAndLogEx(HINT, "Hint: these blocks are ordinary user memory, check the lock bytes");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Verified blocks %d..%d against the tag", MFU_NDEF_CC_BLOCK, MFU_NDEF_CC_BLOCK + 2);
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Format ( " _GREEN_("ok") " )");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefwrite") "` to write a NDEF message");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
// utility function. Retrieves emulator memory
|
|
static int GetMfuDumpFromEMul(mfu_dump_t **buf) {
|
|
|
|
mfu_dump_t *dump = calloc(1, sizeof(mfu_dump_t));
|
|
if (dump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "downloading from emulator memory");
|
|
if (!GetFromDevice(BIG_BUF_EML, (uint8_t *)dump, MFU_MAX_BYTES + MFU_DUMP_PREFIX_LENGTH, 0, NULL, 0, NULL, 2500, false)) {
|
|
PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
|
|
free(dump);
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
*buf = dump ;
|
|
return PM3_SUCCESS ;
|
|
}
|
|
|
|
static int CmdHF14AMfuEView(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu eview",
|
|
"Displays emulator memory\n"
|
|
"By default number of pages shown depends on defined tag type.\n"
|
|
"You can override this with option --end.",
|
|
"hf mfu eview\n"
|
|
"hf mfu eview --end 255 -> dumps whole memory"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0("e", "end", "<dec>", "index of last block"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_param_end
|
|
};
|
|
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
int end = arg_get_int_def(ctx, 1, -1);
|
|
bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 2));
|
|
CLIParserFree(ctx);
|
|
|
|
bool override_end = (end != -1) ;
|
|
|
|
if (override_end && (end < 0 || end > MFU_MAX_BLOCKS)) {
|
|
PrintAndLogEx(WARNING, "Invalid value for end: " _RED_("%d") ". Must be be positive integer < %d", end, MFU_MAX_BLOCKS);
|
|
return PM3_EINVARG ;
|
|
}
|
|
|
|
mfu_dump_t *dump ;
|
|
int res = GetMfuDumpFromEMul(&dump) ;
|
|
if (res != PM3_SUCCESS) {
|
|
return res ;
|
|
}
|
|
|
|
if (override_end) {
|
|
++end ;
|
|
} else {
|
|
end = dump->pages + 1;
|
|
}
|
|
|
|
mfu_print_dump(dump, end, 0, dense_output);
|
|
|
|
if (ndef_detect_message(dump->data, end * MFU_BLOCK_SIZE)) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread")"`");
|
|
}
|
|
|
|
free(dump);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfuESave(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu esave",
|
|
"Saves emulator memory to a MIFARE Ultralight/NTAG dump file (bin/json)\n"
|
|
"By default number of pages saved depends on defined tag type.\n"
|
|
"You can override this with option --end.",
|
|
"hf mfu esave\n"
|
|
"hf mfu esave --end 255 -> saves whole memory\n"
|
|
"hf mfu esave -f hf-mfu-04010203040506-dump"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0("e", "end", "<dec>", "index of last block"),
|
|
arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_param_end
|
|
};
|
|
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
int end = arg_get_int_def(ctx, 1, -1);
|
|
|
|
char filename[FILE_PATH_SIZE];
|
|
int fnlen = 0 ;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
bool override_end = (end != -1) ;
|
|
|
|
if (override_end && (end < 0 || end > MFU_MAX_BLOCKS)) {
|
|
PrintAndLogEx(WARNING, "Invalid value for end:%d. Must be be positive integer <= %d.", end, MFU_MAX_BLOCKS);
|
|
return PM3_EINVARG ;
|
|
}
|
|
|
|
// get dump from memory
|
|
mfu_dump_t *dump ;
|
|
int res = GetMfuDumpFromEMul(&dump) ;
|
|
if (res != PM3_SUCCESS) {
|
|
return res ;
|
|
}
|
|
|
|
// initialize filename
|
|
if (fnlen < 1) {
|
|
PrintAndLogEx(INFO, "Using UID as filename");
|
|
uint8_t uid[7] = {0};
|
|
memcpy(uid, (uint8_t *) & (dump->data), 3);
|
|
memcpy(uid + 3, (uint8_t *) & (dump->data) + 4, 4);
|
|
strcat(filename, "hf-mfu-");
|
|
FillFileNameByUID(filename, uid, "-dump", sizeof(uid));
|
|
}
|
|
|
|
if (override_end) {
|
|
end ++ ;
|
|
} else {
|
|
end = dump->pages ;
|
|
}
|
|
|
|
// save dump. Last block contains PACK + RFU
|
|
uint16_t datalen = (end + 1) * MFU_BLOCK_SIZE + MFU_DUMP_PREFIX_LENGTH;
|
|
res = pm3_save_dump(filename, (uint8_t *)dump, datalen, jsfMfuMemory);
|
|
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
static int CmdHF14AMfuView(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu view",
|
|
"Print a MIFARE Ultralight/NTAG dump file (bin/eml/json)",
|
|
"hf mfu view -f hf-mfu-01020304-dump.bin"
|
|
);
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
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);
|
|
bool dense_output = (g_session.dense_output || arg_get_lit(ctx, 3));
|
|
CLIParserFree(ctx);
|
|
|
|
// read dump file
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 0;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (MFU_MAX_BYTES + MFU_DUMP_PREFIX_LENGTH));
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (bytes_read < MFU_DUMP_PREFIX_LENGTH) {
|
|
PrintAndLogEx(ERR, "Error, dump file is too small");
|
|
free(dump);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = convert_mfu_dump_format(&dump, &bytes_read, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Failed convert on load to new Ultralight/NTAG format");
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
uint16_t block_cnt = ((bytes_read - MFU_DUMP_PREFIX_LENGTH) / MFU_BLOCK_SIZE);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "File: " _YELLOW_("%s"), filename);
|
|
PrintAndLogEx(INFO, "File size %zu bytes, file blocks %d (0x%x)", bytes_read, block_cnt, block_cnt);
|
|
}
|
|
|
|
mfu_dump_t *p = (mfu_dump_t *)dump;
|
|
mfu_print_dump(p, block_cnt, 0, dense_output);
|
|
|
|
// we need to skip prefix
|
|
if (ndef_detect_message(p->data, block_cnt * MFU_BLOCK_SIZE)) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread")"`");
|
|
}
|
|
|
|
free(dump);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfuList(const char *Cmd) {
|
|
return CmdTraceListAlias(Cmd, "hf 14a", "14a -c");
|
|
}
|
|
|
|
static int CmdHF14AAmiibo(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu amiibo",
|
|
"Tries to read all memory from amiibo tag and decrypt it",
|
|
"hf mfu amiiboo --dec -f hf-mfu-04579DB27C4880-dump.bin --> decrypt file\n"
|
|
"hf mfu amiiboo -v --dec --> decrypt tag"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0(NULL, "dec", "Decrypt memory"),
|
|
arg_lit0(NULL, "enc", "Encrypt memory"),
|
|
arg_str0("i", "in", "<fn>", "Specify a filename for input dump file"),
|
|
arg_str0("o", "out", "<fn>", "Specify a filename for output dump file"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool shall_decrypt = arg_get_lit(ctx, 1);
|
|
bool shall_encrypt = arg_get_lit(ctx, 2);
|
|
|
|
int infnlen = 0;
|
|
char infilename[FILE_PATH_SIZE];
|
|
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)infilename, FILE_PATH_SIZE, &infnlen);
|
|
|
|
int outfnlen = 0;
|
|
char outfilename[FILE_PATH_SIZE];
|
|
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)outfilename, FILE_PATH_SIZE, &outfnlen);
|
|
|
|
bool verbose = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
// sanity checks
|
|
if ((shall_decrypt + shall_encrypt) > 1) {
|
|
PrintAndLogEx(WARNING, "Only specify decrypt or encrypt");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// load keys
|
|
nfc3d_amiibo_keys_t amiibo_keys;
|
|
if (nfc3d_amiibo_load_keys(&amiibo_keys) == false) {
|
|
PrintAndLogEx(INFO, "loading key file ( " _RED_("fail") " )");
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
int res = PM3_ESOFT;
|
|
|
|
uint8_t original[NFC3D_AMIIBO_SIZE] = {0};
|
|
|
|
// load dump file if available
|
|
if (infnlen > 0) {
|
|
uint8_t *dump = NULL;
|
|
size_t dumplen = 0;
|
|
res = loadFile_safe(infilename, "", (void **)&dump, &dumplen);
|
|
if (res != PM3_SUCCESS) {
|
|
free(dump);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
if (dumplen < MFU_DUMP_PREFIX_LENGTH) {
|
|
PrintAndLogEx(ERR, "Error, dump file is too small");
|
|
free(dump);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = convert_mfu_dump_format(&dump, &dumplen, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Failed convert on load to new Ultralight/NTAG format");
|
|
free(dump);
|
|
return res;
|
|
}
|
|
|
|
const mfu_dump_t *d = (mfu_dump_t *)dump;
|
|
memcpy(original, d->data, sizeof(original));
|
|
free(dump);
|
|
} else {
|
|
uint16_t dlen = 0;
|
|
uint8_t *dump = NULL;
|
|
res = mfu_dump_tag(MAX_NTAG_215, (void **)&dump, &dlen, false);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Failed to dump memory from tag");
|
|
free(dump);
|
|
return res;
|
|
}
|
|
memcpy(original, dump, sizeof(original));
|
|
free(dump);
|
|
}
|
|
|
|
|
|
uint8_t decrypted[NFC3D_AMIIBO_SIZE] = {0};
|
|
if (shall_decrypt) {
|
|
if (nfc3d_amiibo_unpack(&amiibo_keys, original, decrypted) == false) {
|
|
PrintAndLogEx(INFO, "Tag signature ( " _RED_("fail") " )");
|
|
return PM3_ESOFT;
|
|
}
|
|
// print
|
|
if (verbose) {
|
|
for (uint8_t i = 0; i < (NFC3D_AMIIBO_SIZE / 16); i++) {
|
|
PrintAndLogEx(INFO, "[%d] %s", i, sprint_hex_ascii(decrypted + (i * 16), 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shall_encrypt) {
|
|
uint8_t encrypted[NFC3D_AMIIBO_SIZE] = {0};
|
|
nfc3d_amiibo_pack(&amiibo_keys, decrypted, encrypted);
|
|
// print
|
|
if (verbose) {
|
|
for (uint8_t i = 0; i < (NFC3D_AMIIBO_SIZE / 16); i++) {
|
|
PrintAndLogEx(INFO, "[%d] %s", i, sprint_hex_ascii(encrypted + (i * 16), 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outfnlen) {
|
|
// save dump. Last block contains PACK + RFU
|
|
// uint16_t datalen = MFU_BLOCK_SIZE + MFU_DUMP_PREFIX_LENGTH;
|
|
// res = pm3_save_dump(outfilename, (uint8_t *)dump, datalen, jsfMfuMemory);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfuWipe(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu wipe",
|
|
"Wipe card to zeros. It will ignore block0,1,2,3\n"
|
|
"you will need to call it with password in order to wipe the config and sett default pwd/pack\n"
|
|
"Abort by pressing a key\n"
|
|
"New password.... FFFFFFFF\n"
|
|
"New 3-DES key... 49454D4B41455242214E4143554F5946\n"
|
|
"New AES keys... 00000000000000000000000000000000\n",
|
|
"hf mfu wipe\n"
|
|
"hf mfu wipe -k 49454D4B41455242214E4143554F5946\n"
|
|
"hf mfu wipe -k 49454D4B41455242214E4143554F5946 --schann"
|
|
);
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Key for authentication (UL-C 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 2);
|
|
bool use_schann = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
if (ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (ak_len == 4) {
|
|
has_pwd = true;
|
|
} else if (ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t card_mem_size = 0;
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
if (tagtype == MFU_TT_UL_ERROR) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
|
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
// number of pages to WRITE
|
|
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
|
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
|
//add one as maxblks starts at 0
|
|
card_mem_size = UL_MEMORY_ARRAY[idx] + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ul_print_type(tagtype, 0);
|
|
|
|
// GDM / GEN1A / GEN4 / NTAG21x read the key
|
|
if (ak_len == 0) {
|
|
|
|
DropField();
|
|
|
|
int res = get_ulc_3des_key_magic(tagtype, auth_key_ptr);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
PrintAndLogEx(SUCCESS, "Using 3DES key... %s", sprint_hex_inrow(auth_key_ptr, 16));
|
|
has_auth_key = true;
|
|
}
|
|
|
|
DropField();
|
|
|
|
uint8_t keytype = 0;
|
|
if (has_auth_key || has_pwd) {
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
keytype = 1; // UL_C auth
|
|
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
keytype = 3; // UL_AES auth
|
|
} else {
|
|
keytype = 2; // UL_EV1/NTAG auth
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Start wiping...");
|
|
PrintAndLogEx(INFO, "-----+-----------------------------");
|
|
// time to wipe card
|
|
// We skip the first four blocks.
|
|
// block 0,1 - UID
|
|
// block 2 - lock
|
|
// block 3 - OTP
|
|
for (uint8_t i = 4; i < card_mem_size; i++) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
|
|
goto out;
|
|
}
|
|
|
|
uint8_t data[MFU_BLOCK_SIZE];
|
|
memset(data, 0x00, sizeof(data));
|
|
|
|
// UL_C specific
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
// default config?
|
|
|
|
switch (i) {
|
|
case 4:
|
|
memcpy(data, "\x02\x00\x00\x10", 4);
|
|
break;
|
|
case 5:
|
|
memcpy(data, "\x00\x06\x01\x10", 4);
|
|
break;
|
|
case 6:
|
|
memcpy(data, "\x11\xFF\x00\x00", 4);
|
|
break;
|
|
case 42:
|
|
memcpy(data, "\x30\x00\x00\x00", 4);
|
|
break;
|
|
case 44:
|
|
goto ulc;
|
|
}
|
|
}
|
|
|
|
// UL_AES specific
|
|
if ((tagtype & MFU_TT_UL_AES)) {
|
|
// default config?
|
|
|
|
switch (i) {
|
|
case 41:
|
|
memcpy(data, "\x00\x00\x00\x3C", 4);
|
|
break;
|
|
case 42:
|
|
// schann disabled by previous write on block 41
|
|
use_schann = false;
|
|
memcpy(data, "\x8C\x05\x00\x00", 4);
|
|
break;
|
|
case 46:
|
|
// RFU OTP, write will break if already set to non zero
|
|
i = 47;
|
|
break;
|
|
case 48:
|
|
goto ulaes;
|
|
}
|
|
}
|
|
|
|
// UL / NTAG with PWD/PACK
|
|
if ((tagtype & (MFU_TT_UL_EV1_48 | MFU_TT_UL_EV1_128 | MFU_TT_UL_EV1 | MFU_TT_UL_NANO_40 |
|
|
MFU_TT_NTAG_210u | MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C |
|
|
MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216 | MFU_TT_NTAG_216_F |
|
|
MFU_TT_NTAG_223_DNA | MFU_TT_NTAG_223_DNA_SD |
|
|
MFU_TT_NTAG_I2C_1K | MFU_TT_NTAG_I2C_2K | MFU_TT_NTAG_I2C_1K_PLUS | MFU_TT_NTAG_I2C_2K_PLUS
|
|
))) {
|
|
|
|
|
|
// cfg 1
|
|
if (i == card_mem_size - 4) {
|
|
// strong modulation mode disabled
|
|
// pages don't need authentication
|
|
uint8_t cfg1[MFU_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0xFF};
|
|
memcpy(data, cfg1, sizeof(cfg1));
|
|
}
|
|
|
|
// cfg 2
|
|
if (i == card_mem_size - 3) {
|
|
// Unlimited password attempts
|
|
// NFC counter disabled
|
|
// NFC counter not protected
|
|
// user configuration writeable
|
|
// write access is protected with password
|
|
// 05, Virtual Card Type Identifier is default
|
|
uint8_t cfg2[MFU_BLOCK_SIZE] = {0x00, 0x05, 0x00, 0x00};
|
|
memcpy(data, cfg2, sizeof(cfg2));
|
|
}
|
|
|
|
// Set PWD blocks 0xFF FF FF FF
|
|
if (i == card_mem_size - 2) {
|
|
memset(data, 0xFF, sizeof(data));
|
|
}
|
|
|
|
// Since we changed PWD before, we need to use new PWD to set PACK
|
|
// Pack will be all zeros,
|
|
if (i == card_mem_size - 1) {
|
|
memset(auth_key_ptr, 0xFF, ak_len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
int res = PM3_SUCCESS;
|
|
if (res == PM3_ESOFT) {
|
|
res = mfu_write_block(data, MFU_BLOCK_SIZE, keytype, auth_key_ptr, i);
|
|
}
|
|
*/
|
|
|
|
int res = mfu_write_block(data, MFU_BLOCK_SIZE, keytype, auth_key_ptr, i, use_schann);
|
|
|
|
PrintAndLogEx(INFO, " %3d | %s" NOLF, i, sprint_hex(data, MFU_BLOCK_SIZE));
|
|
switch (res) {
|
|
case PM3_SUCCESS: {
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
break;
|
|
}
|
|
case PM3_ESOFT: {
|
|
PrintAndLogEx(NORMAL, "( " _RED_("fail") " )");
|
|
break;
|
|
}
|
|
case PM3_ETIMEOUT:
|
|
default: {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "-----+-----------------------------");
|
|
|
|
mful_setkey_t packet = {
|
|
.has_auth_key = false,
|
|
.use_schann = false,
|
|
.key_index = 0,
|
|
};
|
|
PacketResponseNG resp;
|
|
|
|
ulc:
|
|
|
|
// UL-C - set 3-DES key
|
|
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
|
|
|
uint8_t defaultkey[16] = {
|
|
0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42,
|
|
0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46
|
|
};
|
|
uint8_t *def_key_ptr = SwapEndian64(defaultkey, 16, 8);
|
|
packet.keytype = 1; // UL-C
|
|
memcpy(packet.key, def_key_ptr, 16);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_SETKEY, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_SETKEY, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "Ultralight C new key... " _GREEN_("%s"), sprint_hex_inrow(defaultkey, sizeof(defaultkey)));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed writing key");
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
ulaes:
|
|
// UL_AES specific
|
|
if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
|
// Set AES keys
|
|
uint8_t defaultkey[16] = { 0 };
|
|
uint8_t *def_key_ptr = SwapEndian64(defaultkey, 16, 16);
|
|
packet.keytype = 3; // UL-AES
|
|
packet.key_index = 0;
|
|
memcpy(packet.key, def_key_ptr, 16);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_SETKEY, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_SETKEY, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "Ultralight AES new DataProtKey... " _GREEN_("%s"), sprint_hex_inrow(defaultkey, sizeof(defaultkey)));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed writing key");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
packet.key_index = 1;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_MIFAREU_SETKEY, (uint8_t *)&packet, sizeof(packet));
|
|
if (WaitForResponseTimeout(CMD_HF_MIFAREU_SETKEY, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "Ultralight AES new UIDRetrKey... " _GREEN_("%s"), sprint_hex_inrow(defaultkey, sizeof(defaultkey)));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed writing key");
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu dump --ns") "` to verify");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
|
|
out:
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfUIncr(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu incr",
|
|
"Increment a MIFARE Ultralight Ev1 counter\n"
|
|
"Will read but not increment counter if NTAG is detected",
|
|
"hf mfu incr -c 0 -v 1337\n"
|
|
"hf mfu incr -c 2 -v 0 -k FFFFFFFF");
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("c", "cnt", "<dec>", "Counter index from 0"),
|
|
arg_int1("v", "val", "<dec>", "Value to increment by (0-16777215)"),
|
|
arg_str0("k", "key", "<hex>", "Authentication key (UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
|
arg_lit0("l", NULL, "Swap entered key's endianness"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint8_t counter = arg_get_int_def(ctx, 1, 3);
|
|
uint32_t value = arg_get_u32_def(ctx, 2, 16777216);
|
|
|
|
int ak_len = 0;
|
|
uint8_t authenticationkey[16] = {0x00};
|
|
uint8_t pack[4] = {0, 0, 0, 0};
|
|
CLIGetHexWithReturn(ctx, 3, authenticationkey, &ak_len);
|
|
bool swap_endian = arg_get_lit(ctx, 4);
|
|
bool use_schann = arg_get_lit(ctx, 5);
|
|
CLIParserFree(ctx);
|
|
|
|
bool has_auth_key = false;
|
|
bool has_pwd = false;
|
|
if (ak_len == 16) {
|
|
has_auth_key = true;
|
|
} else if (ak_len == 4) {
|
|
has_pwd = true;
|
|
} else if (ak_len != 0) {
|
|
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_schann && has_auth_key == false) {
|
|
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
|
return PM3_EINVARG;
|
|
}
|
|
uint8_t *auth_key_ptr = authenticationkey;
|
|
|
|
if (counter > 2) {
|
|
PrintAndLogEx(WARNING, "Counter index must be in range 0-2");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (value > 16777215) {
|
|
PrintAndLogEx(WARNING, "Value to increment must be in range 0-16777215");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t increment_cmd[6] = { MIFARE_ULEV1_INCR_CNT, counter, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
for (uint8_t i = 0; i < 3; i++) {
|
|
increment_cmd[i + 2] = (value >> (8 * i)) & 0xff;
|
|
}
|
|
|
|
uint64_t tagtype = GetHF14AMfU_Type();
|
|
uint64_t tags_with_counter_ul = MFU_TT_UL_EV1_48 | MFU_TT_UL_EV1_128 | MFU_TT_UL_EV1 | MFU_TT_UL_AES;
|
|
uint64_t tags_with_counter_ntag = MFU_TT_NTAG_213 | MFU_TT_NTAG_213_F | MFU_TT_NTAG_213_C | MFU_TT_NTAG_213_TT | MFU_TT_NTAG_215 | MFU_TT_NTAG_216;
|
|
if ((tagtype & (tags_with_counter_ul | tags_with_counter_ntag)) == 0) {
|
|
PrintAndLogEx(WARNING, "tag type does not have counters");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
bool is_ntag = (tagtype & tags_with_counter_ntag) != 0;
|
|
if (is_ntag && (counter != 2)) {
|
|
PrintAndLogEx(WARNING, "NTAG only has one counter at index 2");
|
|
DropField();
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// Swap endianness
|
|
if (swap_endian) {
|
|
if (ak_len == 16) {
|
|
if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
|
}
|
|
} else if (ak_len == 4) {
|
|
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
|
}
|
|
}
|
|
|
|
if (has_auth_key) {
|
|
if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "aes", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
} else if (has_pwd) {
|
|
PrintAndLogEx(INFO, "Using %s... " _GREEN_("%s"), "pwd", sprint_hex_inrow(authenticationkey, ak_len));
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
if (ul_auth_select(&card, tagtype, has_auth_key, auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
|
PrintAndLogEx(FAILED, "failed to select card, exiting...");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t current_counter[3] = { 0, 0, 0 };
|
|
int len = ulev1_readCounter(counter, current_counter, sizeof(current_counter), use_schann);
|
|
if (len != sizeof(current_counter)) {
|
|
PrintAndLogEx(FAILED, "failed to read old counter");
|
|
if (is_ntag) {
|
|
PrintAndLogEx(HINT, "Hint: NTAG detected, try reading with password");
|
|
}
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint32_t current_counter_num = current_counter[0] | (current_counter[1] << 8) | (current_counter[2] << 16);
|
|
PrintAndLogEx(INFO, "Current counter... " _GREEN_("%8d") " - " _GREEN_("%s"), current_counter_num, sprint_hex(current_counter, 3));
|
|
|
|
if ((tagtype & tags_with_counter_ntag) != 0) {
|
|
PrintAndLogEx(WARNING, "NTAG detected, unable to manually increment counter");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t resp[1] = { 0x00 };
|
|
if (ul_send_cmd_raw(increment_cmd, sizeof(increment_cmd), resp, sizeof(resp), use_schann) < 0) {
|
|
PrintAndLogEx(FAILED, "failed to increment counter");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t new_counter[3] = { 0, 0, 0 };
|
|
int new_len = ulev1_readCounter(counter, new_counter, sizeof(new_counter), use_schann);
|
|
if (new_len != sizeof(current_counter)) {
|
|
PrintAndLogEx(FAILED, "failed to read new counter");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint32_t new_counter_num = new_counter[0] | (new_counter[1] << 8) | (new_counter[2] << 16);
|
|
PrintAndLogEx(INFO, "New counter....... " _GREEN_("%8d") " - " _GREEN_("%s"), new_counter_num, sprint_hex(new_counter, 3));
|
|
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AMfUeSetBlk(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf mfu esetblk",
|
|
"Set emulator memory page(s). One page = 4 bytes; pass multiple\n"
|
|
"whole pages of data to set consecutive pages from --blk.",
|
|
"hf mfu esetblk --blk 4 -d 04E10CDA\n"
|
|
"hf mfu esetblk --blk 4 -d 04E10CDA993C8048 -> sets pages 4-5\n"
|
|
);
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("b", "blk", "<dec>", "page number to start at"),
|
|
arg_str0("d", "data", "<hex>", "bytes to write, whole pages (multiple of 4 bytes)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int blk = arg_get_int_def(ctx, 1, 0);
|
|
|
|
uint8_t data[MFU_MAX_BYTES] = {0x00};
|
|
int datalen = 0;
|
|
int res = CLIParamHexToBuf(arg_get_str(ctx, 2), data, sizeof(data), &datalen);
|
|
CLIParserFree(ctx);
|
|
if (res) {
|
|
PrintAndLogEx(FAILED, "Error parsing bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (blk < 0) {
|
|
PrintAndLogEx(WARNING, "page number must be positive");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (datalen == 0 || (datalen % MFU_BLOCK_SIZE) != 0) {
|
|
PrintAndLogEx(WARNING, "data must be whole pages (multiples of %d bytes). Got %i", MFU_BLOCK_SIZE, datalen);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int count = datalen / MFU_BLOCK_SIZE;
|
|
|
|
// live MFU emulator data region is MFU_MAX_BYTES (pages 0..254); page 255 is not round-trippable
|
|
if ((blk + count) * MFU_BLOCK_SIZE > MFU_MAX_BYTES) {
|
|
PrintAndLogEx(WARNING, "page range exceeds emulator memory (max page %u)", (MFU_MAX_BYTES / MFU_BLOCK_SIZE) - 1);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// one esetblk is a single CMD_HF_MIFARE_EML_MEMSET; its payload (data + a 4-byte
|
|
// header) must fit the command buffer. Larger sets should use `hf mfu eload`.
|
|
if (datalen > (int)(g_conn.max_cmd_data_size - 4)) {
|
|
PrintAndLogEx(WARNING, "too many pages for one command: max %d pages (%d bytes). Use " _YELLOW_("`hf mfu eload`") " for larger sets",
|
|
(int)((g_conn.max_cmd_data_size - 4) / MFU_BLOCK_SIZE), (int)(g_conn.max_cmd_data_size - 4));
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// MFU emulator page data starts after the 56-byte mfu_dump_t prefix, so shift the
|
|
// page index by MFU_DUMP_PREFIX_LENGTH/MFU_BLOCK_SIZE (=14), width = MFU_BLOCK_SIZE (4).
|
|
res = mf_eml_set_mem_xt(data, blk + (MFU_DUMP_PREFIX_LENGTH / MFU_BLOCK_SIZE), count, MFU_BLOCK_SIZE, 0);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Failed to set emulator memory");
|
|
return res;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Set " _YELLOW_("%d") " page(s) from page " _YELLOW_("%d"), count, blk);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static command_t CommandTable[] = {
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"list", CmdHF14AMfuList, AlwaysAvailable, "List MIFARE Ultralight / NTAG history"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("recovery") " -------------------------"},
|
|
{"keygen", CmdHF14AMfUKeyGen, AlwaysAvailable, "Generate DES/3DES/AES MIFARE diversified keys"},
|
|
{"pwdgen", CmdHF14AMfUPwdGen, AlwaysAvailable, "Generate pwd from known algos"},
|
|
{"otptear", CmdHF14AMfuOtpTearoff, IfPm3Iso14443a, "Tear-off test on OTP bits"},
|
|
{"countertear", CmdHF14AMfuEv1CounterTearoff, IfPm3Iso14443a, "Tear-off test on Ev1/NTAG Counter bits"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"},
|
|
{"cauth", CmdHF14AMfUCAuth, IfPm3Iso14443a, "Ultralight-C - Authentication"},
|
|
{"cchk", CmdHF14AMfUCAuthChk, IfPm3Iso14443a, "Ultralight-C - Authentication dictionary check"},
|
|
{"desbrute", CmdHF14AMfUCDesBrute, AlwaysAvailable, "Ultralight-C - 3DES key segment brute force"},
|
|
{"aesauth", CmdHF14AMfUAESAuth, IfPm3Iso14443a, "Ultralight-AES - Authentication"},
|
|
{"aeschk", CmdHF14AMfUAESAuthChk, IfPm3Iso14443a, "Ultralight-AES - Authentication dictionary check"},
|
|
{"aesgetuid", CmdHF14AMfUAESGetUID, IfPm3Iso14443a, "Ultralight-AES - Get UID when RID in use"},
|
|
{"setkey", CmdHF14AMfUSetKey, IfPm3Iso14443a, "Ultralight C/AES - Set 3DES/AES keys"},
|
|
{"dump", CmdHF14AMfUDump, IfPm3Iso14443a, "Dump MIFARE Ultralight family tag to binary file"},
|
|
{"incr", CmdHF14AMfUIncr, IfPm3Iso14443a, "Increments Ev1/NTAG counter"},
|
|
{"info", CmdHF14AMfUInfo, IfPm3Iso14443a, "Tag information"},
|
|
{"ndefformat", CmdHF14MfuNDEFFormat, IfPm3Iso14443a, "Format tag as NDEF, writes the Capability Container"},
|
|
{"ndefread", CmdHF14MfuNDEFRead, IfPm3Iso14443a, "Prints NDEF records from card"},
|
|
{"ndefwrite", CmdHF14MfuNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"},
|
|
{"rdbl", CmdHF14AMfURdBl, IfPm3Iso14443a, "Read block"},
|
|
{"restore", CmdHF14AMfURestore, IfPm3Iso14443a, "Restore a dump file onto a tag"},
|
|
{"tamper", CmdHF14MfUTamper, IfPm3Iso14443a, "NTAG 213TT - Configure the tamper feature"},
|
|
{"view", CmdHF14AMfuView, AlwaysAvailable, "Display content from tag dump file"},
|
|
{"wipe", CmdHF14AMfuWipe, IfPm3Iso14443a, "Wipe card to zeros and default key"},
|
|
{"wrbl", CmdHF14AMfUWrBl, IfPm3Iso14443a, "Write block"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("simulation") " -----------------------"},
|
|
{"eload", CmdHF14AMfUeLoad, IfPm3Iso14443a, "Upload file into emulator memory"},
|
|
{"esave", CmdHF14AMfuESave, IfPm3Iso14443a, "Save emulator memory to file"},
|
|
{"eview", CmdHF14AMfuEView, IfPm3Iso14443a, "View emulator memory"},
|
|
{"esetblk", CmdHF14AMfUeSetBlk, IfPm3Iso14443a, "Set emulator memory block"},
|
|
{"sim", CmdHF14AMfUSim, IfPm3Iso14443a, "Simulate MIFARE Ultralight from emulator memory"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("magic") " ----------------------------"},
|
|
{"setuid", CmdHF14AMfUCSetUid, IfPm3Iso14443a, "Set UID - MAGIC tags only"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("amiibo") " ----------------------------"},
|
|
{"amiibo", CmdHF14AAmiibo, IfPm3Iso14443a, "Amiibo tag operations"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
static int CmdHelp(const char *Cmd) {
|
|
(void)Cmd; // Cmd is not used so far
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHFMFUltra(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|