mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 08:04:44 +00:00
73799a6c69
The selectaid_response buffer in `hf 14a simaid` was capped at 100 bytes, which is well below the ISO 14443A frame maximum of 256 bytes and prevents users from emulating tags whose SELECT AID response is larger than a trivial status word. This bumps selectaid_response from 100 to 256 in all three locations (ARM packet handler, client locals, client payload struct) along with the argparse help text. getdata_response is left at 100. The payload struct overlays PM3_CMD_DATA_SIZE (512) via packet->data.asBytes; with the new sizes it totals 435 bytes, so it still fits. Pairs with the earlier tosend bounds check / DYNAMIC_MODULATION_BUFFER2 fix, which raised the actual transmit ceiling to the 256-byte frame limit — this change lets callers supply a response up to that limit.
4593 lines
176 KiB
C
4593 lines
176 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 ISO14443A commands
|
|
//-----------------------------------------------------------------------------
|
|
#include "cmdhf14a.h"
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include "cmdparser.h" // command_t
|
|
#include "commonutil.h" // ARRAYLEN
|
|
#include "comms.h" // clearCommandBuffer
|
|
#include "util.h" // str_ndup
|
|
#include "cmdtrace.h"
|
|
#include "cliparser.h"
|
|
#include "cmdhfmf.h"
|
|
#include "cmdhfmfu.h"
|
|
#include "iso7816/iso7816core.h"
|
|
#include "emv/emvcore.h"
|
|
#include "ui.h"
|
|
#include "crc16.h"
|
|
#include "util_posix.h" // msclock
|
|
#include "aidsearch.h"
|
|
#include "cmdhf.h" // handle HF plot
|
|
#include "cliparser.h"
|
|
#include "protocols.h" // definitions of ISO14A/7816 protocol, MAGIC_GEN_1A
|
|
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
|
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
|
|
#include "cmdnfc.h" // print_type4_cc_info
|
|
#include "fileutils.h" // saveFile
|
|
#include "atrs.h" // getATRinfo
|
|
#include "desfire.h" // desfire enums
|
|
#include "mifare/desfirecore.h" // desfire context
|
|
#include "mifare/mifaredefault.h"
|
|
#include "preferences.h" // get/set device debug level
|
|
#include "pm3_cmd.h"
|
|
#include "mbedtls/cmac.h"
|
|
#include "jansson.h" // JSON parsing
|
|
#include "pla.h" // ECP parsing
|
|
|
|
static bool g_apdu_in_framing_enable = true;
|
|
bool Get_apdu_in_framing(void) {
|
|
return g_apdu_in_framing_enable;
|
|
}
|
|
void Set_apdu_in_framing(bool v) {
|
|
g_apdu_in_framing_enable = v;
|
|
}
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
static int waitCmd(bool i_select, uint32_t timeout, bool verbose);
|
|
|
|
|
|
// based on ISO/IEC JTC1/SC17 STANDING DOCUMENT 5 (Updated 20 September 2024) Register of IC manufacturers
|
|
static const manufactureName_t manufactureMapping[] = {
|
|
// ID, "Vendor Country"
|
|
{ 0x01, "Motorola UK" },
|
|
{ 0x02, "ST Microelectronics SA France" },
|
|
{ 0x03, "Hitachi, Ltd Japan" },
|
|
{ 0x04, "NXP Semiconductors Germany" },
|
|
{ 0x05, "Infineon Technologies AG Germany" },
|
|
{ 0x06, "Cylink USA" },
|
|
{ 0x07, "Texas Instrument France" },
|
|
{ 0x08, "Fujitsu Limited Japan" },
|
|
{ 0x09, "Matsushita Electronics Corporation, Semiconductor Company Japan" },
|
|
{ 0x0A, "NEC Japan" },
|
|
{ 0x0B, "Oki Electric Industry Co. Ltd Japan" },
|
|
{ 0x0C, "Toshiba Corp. Japan" },
|
|
{ 0x0D, "Mitsubishi Electric Corp. Japan" },
|
|
{ 0x0E, "Samsung Electronics Co. Ltd Korea" },
|
|
{ 0x0F, "Hynix / Hyundai, Korea" },
|
|
{ 0x10, "LG-Semiconductors Co. Ltd Korea" },
|
|
{ 0x11, "Emosyn-EM Microelectronics USA" },
|
|
{ 0x12, "Wisekey Semiconductors (previously INSIDE Technology) France" },
|
|
{ 0x13, "ORGA Kartensysteme GmbH Germany" },
|
|
{ 0x14, "SHARP Corporation Japan" },
|
|
{ 0x15, "ATMEL France" },
|
|
{ 0x16, "EM Microelectronic-Marin SA Switzerland" },
|
|
{ 0x17, "SMARTRAC TECHNOLOGY GmbH Germany" },
|
|
{ 0x18, "ZMD AG Germany" },
|
|
{ 0x19, "XICOR, Inc. USA" },
|
|
{ 0x1A, "Sony Corporation Japan" },
|
|
{ 0x1B, "Malaysia Microelectronic Solutions Sdn. Bhd Malaysia" },
|
|
{ 0x1C, "Emosyn USA" },
|
|
{ 0x1D, "Shanghai Fudan Microelectronics Co. Ltd. P.R. China" },
|
|
{ 0x1E, "Magellan Technology Pty Limited Australia" },
|
|
{ 0x1F, "Melexis NV BO Switzerland" },
|
|
{ 0x20, "Renesas Technology Corp. Japan" },
|
|
{ 0x21, "TAGSYS France" },
|
|
{ 0x22, "Transcore USA" },
|
|
{ 0x23, "Shanghai belling corp., ltd. China" },
|
|
{ 0x24, "Masktech Germany Gmbh Germany" },
|
|
{ 0x25, "Innovision Research and Technology Plc UK" },
|
|
{ 0x26, "Hitachi ULSI Systems Co., Ltd. Japan" },
|
|
{ 0x27, "Yubico AB Sweden" },
|
|
{ 0x28, "Ricoh Japan" },
|
|
{ 0x29, "ASK France" },
|
|
{ 0x2A, "Unicore Microsystems, LLC Russian Federation" },
|
|
{ 0x2B, "Dallas Semiconductor/Maxim USA" },
|
|
{ 0x2C, "Impinj, Inc. USA" },
|
|
{ 0x2D, "RightPlug Alliance USA" },
|
|
{ 0x2E, "Broadcom Corporation USA" },
|
|
{ 0x2F, "MStar Semiconductor, Inc Taiwan, ROC" },
|
|
{ 0x30, "BeeDar Technology Inc. USA" },
|
|
{ 0x31, "RFIDsec Denmark" },
|
|
{ 0x32, "Schweizer Electronic AG Germany" },
|
|
{ 0x33, "AMIC Technology Corp Taiwan" },
|
|
{ 0x34, "Mikron JSC Russia" },
|
|
{ 0x35, "Fraunhofer Institute for Photonic Microsystems Germany" },
|
|
{ 0x36, "IDS Microchip AG Switzerland" },
|
|
{ 0x37, "Kovio USA" },
|
|
{ 0x38, "HMT Microelectronic Ltd Switzerland" },
|
|
{ 0x39, "Silicon Craft Technology Thailand" },
|
|
{ 0x3A, "Advanced Film Device Inc. Japan" },
|
|
{ 0x3B, "Nitecrest Ltd UK" },
|
|
{ 0x3C, "Verayo Inc. USA" },
|
|
{ 0x3D, "HID Global USA" },
|
|
{ 0x3E, "Productivity Engineering Gmbh Germany" },
|
|
{ 0x3F, "Austriamicrosystems AG (reserved) Austria" },
|
|
{ 0x40, "Thales DIS (previously Gemalto SA) France" },
|
|
{ 0x41, "Renesas Electronics Corporation Japan" },
|
|
{ 0x42, "3Alogics Inc Korea" },
|
|
{ 0x43, "Top TroniQ Asia Limited Hong Kong" },
|
|
{ 0x44, "Gentag Inc. USA" },
|
|
{ 0x45, "Invengo Information Technology Co. Ltd China" },
|
|
{ 0x46, "Guangzhou Sysur Microelectronics, Inc China" },
|
|
{ 0x47, "CEITEC S.A. Brazil" },
|
|
{ 0x48, "Shanghai Quanray Electronics Co. Ltd. China" },
|
|
{ 0x49, "MediaTek Inc Taiwan" },
|
|
{ 0x4A, "Angstrem PJSC Russia" },
|
|
{ 0x4B, "Celisic Semiconductor (Hong Kong) Limited China" },
|
|
{ 0x4C, "LEGIC Identsystems AG Switzerland" },
|
|
{ 0x4D, "Balluff GmbH Germany" },
|
|
{ 0x4E, "Oberthur Technologies France" },
|
|
{ 0x4F, "Silterra Malaysia Sdn. Bhd. Malaysia" },
|
|
{ 0x50, "Presto Engineering Denmark" },
|
|
{ 0x51, "Giesecke & Devrient GmbH Germany" },
|
|
{ 0x52, "Shenzhen China Vision Microelectronics Co., Ltd. China" },
|
|
{ 0x53, "Shanghai Feiju Microelectronics Co. Ltd. China" },
|
|
{ 0x54, "Intel Corporation USA" },
|
|
{ 0x55, "Microsensys GmbH Germany" },
|
|
{ 0x56, "Sonix Technology Co., Ltd. Taiwan" },
|
|
{ 0x57, "Qualcomm Technologies Inc USA" },
|
|
{ 0x58, "Realtek Semiconductor Corp Taiwan" },
|
|
{ 0x59, "Freevision Technologies Co. Ltd China" },
|
|
{ 0x5A, "Giantec Semiconductor Inc. China" },
|
|
{ 0x5B, "JSC Angstrem-T Russia" },
|
|
{ 0x5C, "STARCHIP France" },
|
|
{ 0x5D, "SPIRTECH France" },
|
|
{ 0x5E, "GANTNER Electronic GmbH Austria" },
|
|
{ 0x5F, "Nordic Semiconductor Norway" },
|
|
{ 0x60, "Verisiti Inc USA" },
|
|
{ 0x61, "Wearlinks Technology Inc. China" },
|
|
{ 0x62, "Userstar Information Systems Co., Ltd Taiwan" },
|
|
{ 0x63, "Pragmatic Printing Ltd. UK" },
|
|
{ 0x64, "Associação do Laboratório de Sistemas Integráveis Tecnológico - LSI-TEC Brazil" },
|
|
{ 0x65, "Tendyron Corporation China" },
|
|
{ 0x66, "MUTO Smart Co., Ltd. Korea" },
|
|
{ 0x67, "ON Semiconductor USA" },
|
|
{ 0x68, "TÜBITAK BILGEM Turkey" }, // Don't use "İ", Proxspace doesn't like it
|
|
{ 0x69, "Huada Semiconductor Co., Ltd China" },
|
|
{ 0x6A, "SEVENEY France" },
|
|
{ 0x6B, "THALES DIS Design Services SAS (previously ISSM) France" },
|
|
{ 0x6C, "Wisesec Ltd Israel" },
|
|
{ 0x6D, "LTD \"NM-Teh\" Russia" },
|
|
{ 0x70, "ifm electronic gmbh Germany" },
|
|
{ 0x71, "Sichuan Kiloway Technologies Co., Ltd. China" },
|
|
{ 0x72, "Ford Motor Company US" },
|
|
{ 0x73, "Beijing Tsingteng MicroSystem Co.,Ltd China" },
|
|
{ 0x74, "Huada EverCore Co., Ltd China" },
|
|
{ 0x75, "Smartchip Microelectronics Corporation Taiwan" },
|
|
{ 0x76, "Tongxin Microelectronics Co., Ltd. China" },
|
|
{ 0x77, "Ningbo IOT Microelectronics Co Ltd China" },
|
|
{ 0x78, "AU Optronics Taiwan" },
|
|
{ 0x79, "CUBIC USA" },
|
|
{ 0x7A, "Abbott Diabetes Care USA" },
|
|
{ 0x7B, "Shenzen Nation RFID Technology Co Ltd China" },
|
|
{ 0x7C, "DB HiTek Co Ltd Korea" },
|
|
{ 0x7D, "SATO Vicinity Australia" },
|
|
{ 0x7E, "Holtek Taiwan" },
|
|
// Previously, following entries were listed in the doc as 0x7f, 0x80 etc.
|
|
// Now, they are listed as 'FF 00', 'FF 01',...
|
|
{ 0x7F, "Shenzhen Goodix Technology Co., Ltd. China" },
|
|
{ 0x80, "Panthronics AG Austria" },
|
|
{ 0x81, "Beijing Huada Infosec Technology Co., Ltd China" },
|
|
{ 0x82, "Shanghai Oriental Magnetic Card Engineering Co Ltd. China" },
|
|
{ 0x83, "8ApeX Inc USA" },
|
|
{ 0x84, "Abbott Ireland" },
|
|
{ 0x85, "Proqure Inc USA" },
|
|
{ 0x86, "Schreiner Group GmbH & Co. KG Germany" },
|
|
{ 0x87, "Beijing SmartChip Microelectronics Technology Company Limited China" },
|
|
{ 0x88, "Datang Microelectronics Technology Co., Ltd. China" },
|
|
{ 0x89, "Wise Security Technology (Guangzhou) Co., Ltd. China" },
|
|
{ 0x8A, "CEC Huada Electronic Design Co., Ltd. China" },
|
|
{ 0x8B, "Shanghai Techsun RFID Technology Co., Ltd. China" },
|
|
{ 0x8C, "North China Institute of Computing Technology China" },
|
|
{ 0x8D, "Shanghai Huahong Integrated Circuit Co., Ltd. China" },
|
|
{ 0x8E, "Shanghai MintSilicon Microelectronics Inc., Ltd. China" },
|
|
{ 0x8F, "Xinsheng Technology Co., Ltd. China" },
|
|
{ 0x90, "IDEX Biometrics ASA Norway" },
|
|
{ 0x91, "Novo Nordisk A/S Denmark" },
|
|
{ 0x92, "Shandong Huayi Micro-Electronics Technology Co., Ltd. China" },
|
|
{ 0x93, "Abbott Heart Failure USA" },
|
|
{ 0x94, "P&M Information Technology (Shenzhen) Co., Ltd. China" },
|
|
{ 0x95, "MARS TECHNOLOGY PTE. LTD. Singapore" },
|
|
{ 0x96, "Trovan Limited Isle of Man" },
|
|
{ 0x00, "no tag-info available" } // must be the last entry
|
|
};
|
|
|
|
// get a product description based on the UID
|
|
// uid[8] tag uid
|
|
// returns description of the best match
|
|
const char *getTagInfo(uint8_t uid) {
|
|
|
|
for (int i = 0; i < ARRAYLEN(manufactureMapping); ++i) {
|
|
if (uid == manufactureMapping[i].uid) {
|
|
return manufactureMapping[i].desc;
|
|
}
|
|
}
|
|
|
|
//No match, return default
|
|
return manufactureMapping[ARRAYLEN(manufactureMapping) - 1].desc;
|
|
}
|
|
|
|
static const hintAIDList_t hintAIDList[] = {
|
|
{ "\xA0\x00\x00\x06\x47\x2F\x00\x01", 8, "", 0, "FIDO", "hf fido" },
|
|
{ "\xA0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00", 11, "", 0, "PIV", "" },
|
|
{ "\xD2\x76\x00\x01\x24\x01", 8, "", 0, "OpenPGP", "" },
|
|
{ "\x31\x50\x41\x59\x2E\x53\x59\x53\x2E\x44\x44\x46\x30\x31", 14, "", 0, "EMV (pse)", "emv" },
|
|
{ "\x32\x50\x41\x59\x2E\x53\x59\x53\x2E\x44\x44\x46\x30\x31", 14, "", 0, "EMV (ppse)", "emv" },
|
|
{ "\x41\x44\x20\x46\x31", 5, "", 0, "CIPURSE", "hf cipurse" },
|
|
{ "\xA0\x00\x00\x09\x09\xAC\xCE\x55\x01", 9, "", 0, "Aliro", "hf aliro" },
|
|
{ "\xd2\x76\x00\x00\x85\x01\x00", 7, "", 0, "desfire", "hf mfdes" },
|
|
{ "\x4F\x53\x45\x2E\x56\x41\x53\x2E\x30\x31", 10, "ApplePay", 8, "OSE.VAS (Apple Wallet)", "hf vas" },
|
|
{ "\x4F\x53\x45\x2E\x56\x41\x53\x2E\x30\x31", 10, "AndroidPay", 10, "OSE.VAS (Google Wallet)", "hf gst" },
|
|
{ "\xA0\x00\x00\x04\x76\xD0\x00\x01\x11", 9, "", 0, "Google Smart Tap v2", "hf gst" },
|
|
};
|
|
|
|
// iso14a apdu input frame length
|
|
static uint16_t gs_frame_len = 0;
|
|
static uint8_t gs_frames_num = 0;
|
|
static uint16_t atsFSC[] = {16, 24, 32, 40, 48, 64, 96, 128, 256};
|
|
|
|
int hf14a_getversion_data(iso14a_card_select_t *card, uint64_t select_status, version_hw_t *hw) {
|
|
|
|
// field on, card selected if select_status is 1 or 4, not selected if 2
|
|
int res = PM3_EFAILED;
|
|
|
|
// if 4b UID or NXP, try to get version
|
|
if ((card->uidlen == 4) || ((card->uidlen == 7) && (card->uid[0] == 0x04))) {
|
|
// GetVersion
|
|
if ((select_status == 1) || (select_status == 4)) { // L4
|
|
|
|
uint8_t response[PM3_CMD_DATA_SIZE] = {0};
|
|
int resp_len = 0;
|
|
uint8_t getVersion[5] = {0x90, 0x60, 0x00, 0x00, 0x00};
|
|
res = ExchangeAPDU14a(getVersion, sizeof(getVersion), false, false, response, sizeof(response), &resp_len);
|
|
DropField();
|
|
|
|
if (res == PM3_ETIMEOUT) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp_len == 9) {
|
|
memcpy(hw, response, sizeof(version_hw_t));
|
|
return PM3_SUCCESS;
|
|
}
|
|
return PM3_EFAILED;
|
|
|
|
}
|
|
|
|
// select_status = 2, L3
|
|
uint8_t version[8] = {0};
|
|
uint8_t uid[7] = {0};
|
|
res = mfu_get_version_uid(version, uid);
|
|
DropField();
|
|
if (res == PM3_SUCCESS) {
|
|
memcpy(hw, version + 1, sizeof(version_hw_t));
|
|
}
|
|
}
|
|
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
static int CmdHF14AList(const char *Cmd) {
|
|
return CmdTraceListAlias(Cmd, "hf 14a", "14a -c");
|
|
}
|
|
|
|
int hf14a_getconfig(hf14a_config_t *config) {
|
|
if (!g_session.pm3_present) return PM3_ENOTTY;
|
|
|
|
if (config == NULL) {
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
|
|
SendCommandNG(CMD_HF_ISO14443A_GET_CONFIG, NULL, 0);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_ISO14443A_GET_CONFIG, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
memcpy(config, resp.data.asBytes, sizeof(hf14a_config_t));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int hf14a_setconfig(hf14a_config_t *config, bool verbose) {
|
|
if (!g_session.pm3_present) return PM3_ENOTTY;
|
|
|
|
clearCommandBuffer();
|
|
if (config != NULL) {
|
|
SendCommandNG(CMD_HF_ISO14443A_SET_CONFIG, (uint8_t *)config, sizeof(hf14a_config_t));
|
|
if (verbose) {
|
|
SendCommandNG(CMD_HF_ISO14443A_PRINT_CONFIG, NULL, 0);
|
|
}
|
|
} else {
|
|
SendCommandNG(CMD_HF_ISO14443A_PRINT_CONFIG, NULL, 0);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int hf_14a_config_example(void) {
|
|
PrintAndLogEx(NORMAL, "\nExamples to revive Gen2/DirectWrite magic cards failing at anticollision:");
|
|
PrintAndLogEx(NORMAL, _CYAN_(" MFC 1k 4b UID")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 skip --rats skip"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf mf wrbl --blk 0 -k FFFFFFFFFFFF -d 11223344440804006263646566676869"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" MFC 4k 4b UID")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 skip --rats skip"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf mf wrbl --blk 0 -k FFFFFFFFFFFF -d 11223344441802006263646566676869"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" MFC 1k 7b UID")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 force --cl3 skip --rats skip"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf mf wrbl --blk 0 -k FFFFFFFFFFFF -d 04112233445566084400626364656667"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" MFC 4k 7b UID")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 force --cl3 skip --rats skip"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf mf wrbl --blk 0 -k FFFFFFFFFFFF -d 04112233445566184200626364656667"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" MFUL ")"/" _CYAN_(" MFUL EV1 ")"/" _CYAN_(" MFULC")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --atqa force --bcc ignore --cl2 force --cl3 skip -rats skip"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf mfu setuid --uid 04112233445566"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --std"));
|
|
|
|
PrintAndLogEx(NORMAL, "\nExamples of polling loop annotations used to enable anticollision on mobile targets:");
|
|
PrintAndLogEx(NORMAL, _CYAN_(" ECP Express Transit TFL/EMV")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla ecp.transit.emv"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a02c801000300027900000000"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" ECP Express Transit Navigo")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla ecp.transit.navigo"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a02c8010003095a0000000000"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" ECP VAS Only")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla ecp.vasonly"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a01000002"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" ECP Access Wildcard")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla ecp.access:02ffff"));
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla 6a02c3020002ffff"));
|
|
PrintAndLogEx(NORMAL, _CYAN_(" ECP GymKit")":");
|
|
PrintAndLogEx(NORMAL, _YELLOW_(" hf 14a config --pla ecp.gymkit"));
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
static int CmdHf14AConfig(const char *Cmd) {
|
|
if (!g_session.pm3_present) return PM3_ENOTTY;
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a config",
|
|
"Configure 14a settings (use with caution)\n"
|
|
" `-v` also prints examples for reviving Gen2 cards & configuring polling loop annotations",
|
|
"hf 14a config -> Print current configuration\n"
|
|
"hf 14a config --std -> Reset default configuration (follow standard)\n"
|
|
"hf 14a config --atqa std -> Follow standard\n"
|
|
"hf 14a config --atqa force -> Force execution of anticollision\n"
|
|
"hf 14a config --atqa skip -> Skip anticollision\n"
|
|
"hf 14a config --bcc std -> Follow standard\n"
|
|
"hf 14a config --bcc fix -> Fix bad BCC in anticollision\n"
|
|
"hf 14a config --bcc ignore -> Ignore bad BCC and use it as such\n"
|
|
"hf 14a config --cl2 std -> Follow standard\n"
|
|
"hf 14a config --cl2 force -> Execute CL2\n"
|
|
"hf 14a config --cl2 skip -> Skip CL2\n"
|
|
"hf 14a config --cl3 std -> Follow standard\n"
|
|
"hf 14a config --cl3 force -> Execute CL3\n"
|
|
"hf 14a config --cl3 skip -> Skip CL3\n"
|
|
"hf 14a config --rats std -> Follow standard\n"
|
|
"hf 14a config --rats force -> Execute RATS\n"
|
|
"hf 14a config --rats skip -> Skip RATS\n"
|
|
"hf 14a config --mag on -> Enable Apple magsafe polling\n"
|
|
"hf 14a config --mag off -> Disable Apple magsafe polling\n"
|
|
"hf 14a config --pla <hex> -> Set polling loop annotation (max 22 bytes)\n"
|
|
"hf 14a config --pla off -> Disable polling loop annotation\n"
|
|
"hf 14a config --pla ecp.access.[tci] -> ECP Access (default TCI: ffff)\n"
|
|
"hf 14a config --pla ecp.transit.[tci/name] -> ECP Transit (emv/tfl/navigo or hex TCI)\n"
|
|
"hf 14a config --pla ecp.(vasorpay/vasandpay/vasonly/payonly) -> ECP VAS\n"
|
|
"hf 14a config --pla ecp.gymkit -> ECP GymKit\n");
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "atqa", "<std|force|skip>", "Configure ATQA<>anticollision behavior"),
|
|
arg_str0(NULL, "bcc", "<std|fix|ignore>", "Configure BCC behavior"),
|
|
arg_str0(NULL, "cl2", "<std|force|skip>", "Configure SAK<>CL2 behavior"),
|
|
arg_str0(NULL, "cl3", "<std|force|skip>", "Configure SAK<>CL3 behavior"),
|
|
arg_str0(NULL, "rats", "<std|force|skip>", "Configure RATS behavior"),
|
|
arg_str0(NULL, "mag", "<on|off>", "Configure Apple MagSafe polling"),
|
|
arg_str0(NULL, "pla", "<hex|off|ecp>", "Configure polling loop annotation"),
|
|
arg_lit0(NULL, "std", "Reset default configuration: follow all standard"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool defaults = arg_get_lit(ctx, 8);
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
|
|
int vlen = 0;
|
|
char value[64];
|
|
int atqa = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) atqa = 0;
|
|
else if (strcmp(value, "force") == 0) atqa = 1;
|
|
else if (strcmp(value, "skip") == 0) atqa = 2;
|
|
else {
|
|
PrintAndLogEx(ERR, "atqa argument must be 'std', 'force', or 'skip'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
int bcc = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) bcc = 0;
|
|
else if (strcmp(value, "fix") == 0) bcc = 1;
|
|
else if (strcmp(value, "ignore") == 0) bcc = 2;
|
|
else {
|
|
PrintAndLogEx(ERR, "bcc argument must be 'std', 'fix', or 'ignore'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
int cl2 = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) cl2 = 0;
|
|
else if (strcmp(value, "force") == 0) cl2 = 1;
|
|
else if (strcmp(value, "skip") == 0) cl2 = 2;
|
|
else {
|
|
PrintAndLogEx(ERR, "cl2 argument must be 'std', 'force', or 'skip'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
int cl3 = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) cl3 = 0;
|
|
else if (strcmp(value, "force") == 0) cl3 = 1;
|
|
else if (strcmp(value, "skip") == 0) cl3 = 2;
|
|
else {
|
|
PrintAndLogEx(ERR, "cl3 argument must be 'std', 'force', or 'skip'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
int rats = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) rats = 0;
|
|
else if (strcmp(value, "force") == 0) rats = 1;
|
|
else if (strcmp(value, "skip") == 0) rats = 2;
|
|
else {
|
|
PrintAndLogEx(ERR, "rats argument must be 'std', 'force', or 'skip'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
int magsafe = defaults ? 0 : -1;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)value, sizeof(value), &vlen);
|
|
if (vlen > 0) {
|
|
if (strcmp(value, "std") == 0) magsafe = 0;
|
|
else if (strcmp(value, "skip") == 0) magsafe = 0;
|
|
else if (strcmp(value, "disable") == 0) magsafe = 0;
|
|
else if (strcmp(value, "off") == 0) magsafe = 0;
|
|
else if (strcmp(value, "enable") == 0) magsafe = 1;
|
|
else if (strcmp(value, "on") == 0) magsafe = 1;
|
|
else {
|
|
PrintAndLogEx(ERR, "magsafe argument must be 'std', 'skip', 'off', 'disable', 'on' or 'enable'");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
// Handle polling loop annotation parameter
|
|
iso14a_polling_frame_t pla = {
|
|
// 0 signals that PLA has to be disabled, -1 signals that no change has to be made
|
|
.frame_length = defaults ? 0 : -1,
|
|
.last_byte_bits = 8,
|
|
.extra_delay = 5
|
|
};
|
|
|
|
// Get main --pla value
|
|
CLIParamStrToBuf(arg_get_str(ctx, 7), (uint8_t *)value, sizeof(value), &vlen);
|
|
str_lower((char *)value);
|
|
|
|
if (vlen > 0) {
|
|
if (strncmp((char *)value, "std", 3) == 0) pla.frame_length = 0;
|
|
else if (strncmp((char *)value, "skip", 4) == 0) pla.frame_length = 0;
|
|
else if (strncmp((char *)value, "disable", 3) == 0) pla.frame_length = 0;
|
|
else if (strncmp((char *)value, "off", 3) == 0) pla.frame_length = 0;
|
|
else if (strncmp((char *)value, "ecp", 3) == 0) {
|
|
// Parse ECP subcommand
|
|
int length = pla_parse_ecp_subcommand((char *)value, pla.frame, sizeof(pla.frame));
|
|
if (length < 0) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
pla.frame_length = length;
|
|
|
|
// Add CRC
|
|
uint8_t first, second;
|
|
compute_crc(CRC_14443_A, pla.frame, pla.frame_length, &first, &second);
|
|
pla.frame[pla.frame_length++] = first;
|
|
pla.frame[pla.frame_length++] = second;
|
|
PrintAndLogEx(INFO, "Set polling loop annotation to ECP: %s", sprint_hex(pla.frame, pla.frame_length));
|
|
} else {
|
|
// Convert hex string to bytes
|
|
int length = 0;
|
|
if (param_gethex_to_eol((char *)value, 0, pla.frame, sizeof(pla.frame), &length) != 0) {
|
|
PrintAndLogEx(ERR, "Error parsing polling loop annotation bytes");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
pla.frame_length = length;
|
|
|
|
// Validate length before adding CRC
|
|
if (pla.frame_length < 1 || pla.frame_length > 22) {
|
|
PrintAndLogEx(ERR, "Polling loop annotation length invalid: min %d; max %d", 1, 22);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t first, second;
|
|
compute_crc(CRC_14443_A, pla.frame, pla.frame_length, &first, &second);
|
|
pla.frame[pla.frame_length++] = first;
|
|
pla.frame[pla.frame_length++] = second;
|
|
PrintAndLogEx(INFO, "Set polling loop annotation to: %s", sprint_hex(pla.frame, pla.frame_length));
|
|
}
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
// Handle empty command
|
|
if (strlen(Cmd) == 0) {
|
|
return hf14a_setconfig(NULL, verbose);
|
|
}
|
|
|
|
if (verbose) {
|
|
hf_14a_config_example();
|
|
}
|
|
|
|
// Initialize config with all parameters
|
|
hf14a_config_t config = {
|
|
.forceanticol = atqa,
|
|
.forcebcc = bcc,
|
|
.forcecl2 = cl2,
|
|
.forcecl3 = cl3,
|
|
.forcerats = rats,
|
|
.magsafe = magsafe,
|
|
.polling_loop_annotation = pla
|
|
};
|
|
|
|
return hf14a_setconfig(&config, verbose);
|
|
}
|
|
|
|
static const char *get_uid_type(iso14a_card_select_t *card) {
|
|
|
|
static char s[60] = {0};
|
|
memset(s, 0, sizeof(s));
|
|
|
|
switch (card->uidlen) {
|
|
case 4: {
|
|
if (card->uid[0] == 0x08) {
|
|
sprintf(s, " ( RID - random ID )");
|
|
} else if ((card->uid[0] & 0xF) == 0xF) {
|
|
sprintf(s, " ( FNUID, fixed, non-unique ID )");
|
|
} else if (card->uid[0] == 0x88) {
|
|
sprintf(s, " ( Cascade tag - not final )");
|
|
} else if (card->uid[0] == 0xF8) {
|
|
sprintf(s, " ( RFU )");
|
|
} else {
|
|
sprintf(s, " ( ONUID, re-used )");
|
|
}
|
|
break;
|
|
}
|
|
case 7: {
|
|
sprintf(s, " ( double )");
|
|
break;
|
|
}
|
|
case 10: {
|
|
sprintf(s, " ( triple )");
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
int Hf14443_4aGetCardData(iso14a_card_select_t *card) {
|
|
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE, 0, 0, NULL, 0);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
memcpy(card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision
|
|
|
|
if (select_status == 0) {
|
|
PrintAndLogEx(ERR, "iso14443a card select failed");
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
if (select_status == 2) {
|
|
PrintAndLogEx(ERR, "Card doesn't support iso14443-4 mode");
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
if (select_status == 3) {
|
|
PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision");
|
|
// identify TOPAZ
|
|
if (card->atqa[1] == 0x0C && card->atqa[0] == 0x00) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf topaz info`"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "\tATQA : %02X %02X", card->atqa[1], card->atqa[0]);
|
|
}
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card->uid, card->uidlen));
|
|
PrintAndLogEx(SUCCESS, "ATQA: %02X %02X", card->atqa[1], card->atqa[0]);
|
|
PrintAndLogEx(SUCCESS, " SAK: %02X [%" PRIu64 "]", card->sak, resp.oldarg[0]);
|
|
|
|
// a valid ATS consists of at least the length byte (TL) and 2 CRC bytes
|
|
if (card->ats_len < 3) {
|
|
PrintAndLogEx(INFO, "Error ATS length(%d) : %s", card->ats_len, sprint_hex(card->ats, card->ats_len));
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
if (card->ats_len == card->ats[0] + 2)
|
|
PrintAndLogEx(SUCCESS, " ATS: [%d] %s", card->ats[0], sprint_hex(card->ats, card->ats[0]));
|
|
else
|
|
PrintAndLogEx(SUCCESS, " ATS: [%d] %s", card->ats_len, sprint_hex(card->ats, card->ats_len));
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AReader(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a reader",
|
|
"Act as a ISO-14443a reader to identify tag. Look for ISO-14443a tags until Enter or the pm3 button is pressed",
|
|
"hf 14a reader\n"
|
|
"hf 14a reader -@ -> Continuous mode\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("k", "keep", "keep the field active after command executed"),
|
|
arg_lit0("s", "silent", "silent (no messages)"),
|
|
arg_lit0(NULL, "drop", "just drop the signal field"),
|
|
arg_lit0(NULL, "skip", "ISO14443-3 select only (skip RATS)"),
|
|
arg_lit0("@", NULL, "continuous reader mode"),
|
|
arg_lit0("w", "wait", "wait for card"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool disconnectAfter = true;
|
|
if (arg_get_lit(ctx, 1)) {
|
|
disconnectAfter = false;
|
|
}
|
|
|
|
bool silent = arg_get_lit(ctx, 2);
|
|
|
|
uint32_t cm = ISO14A_CONNECT | ISO14A_CLEARTRACE;
|
|
if (arg_get_lit(ctx, 3)) {
|
|
cm &= ~ISO14A_CONNECT;
|
|
cm &= ~ISO14A_CLEARTRACE;
|
|
}
|
|
|
|
if (arg_get_lit(ctx, 4)) {
|
|
cm |= ISO14A_NO_RATS;
|
|
}
|
|
|
|
bool continuous = arg_get_lit(ctx, 5);
|
|
bool wait = arg_get_lit(ctx, 6);
|
|
CLIParserFree(ctx);
|
|
|
|
bool found = false;
|
|
if (disconnectAfter == false) {
|
|
cm |= ISO14A_NO_DISCONNECT;
|
|
}
|
|
|
|
if (continuous) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
|
}
|
|
|
|
int res = PM3_SUCCESS;
|
|
do {
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, cm, 0, 0, NULL, 0);
|
|
|
|
if ((cm & ISO14A_CONNECT) == ISO14A_CONNECT) {
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
DropField();
|
|
res = PM3_ESOFT;
|
|
goto plot;
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
/*
|
|
0: couldn't read
|
|
1: OK, with ATS
|
|
2: OK, no ATS
|
|
3: proprietary Anticollision
|
|
*/
|
|
uint64_t select_status = resp.oldarg[0];
|
|
|
|
found = (select_status != 0);
|
|
|
|
if (select_status == 0) {
|
|
DropField();
|
|
res = PM3_ESOFT;
|
|
goto plot;
|
|
}
|
|
|
|
if (select_status == 3) {
|
|
if (!(silent && continuous)) {
|
|
PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision");
|
|
|
|
// identify TOPAZ
|
|
if (card.atqa[1] == 0x0C && card.atqa[0] == 0x00) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf topaz info`"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "ATQA: %02X %02X", card.atqa[1], card.atqa[0]);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
DropField();
|
|
res = PM3_ESOFT;
|
|
goto plot;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen));
|
|
|
|
if (!(silent && continuous)) {
|
|
PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]);
|
|
PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, resp.oldarg[0]);
|
|
|
|
if (card.ats_len >= 3) { // a valid ATS consists of at least the length byte (TL) and 2 CRC bytes
|
|
if (card.ats_len == card.ats[0] + 2)
|
|
PrintAndLogEx(SUCCESS, " ATS: " _GREEN_("%s"), sprint_hex(card.ats, card.ats[0]));
|
|
else {
|
|
PrintAndLogEx(SUCCESS, " ATS: [%d] " _GREEN_("%s"), card.ats_len, sprint_hex(card.ats, card.ats_len));
|
|
}
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
if ((disconnectAfter == false) && (silent == false)) {
|
|
PrintAndLogEx(SUCCESS, "Card is selected. You can now start sending commands");
|
|
}
|
|
}
|
|
plot:
|
|
if (continuous) {
|
|
res = handle_hf_plot(false);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(DEBUG, "plot failed");
|
|
}
|
|
}
|
|
|
|
if (kbd_enter_pressed()) {
|
|
break;
|
|
}
|
|
|
|
} while (continuous || (wait && (!found)));
|
|
|
|
if (disconnectAfter == false) {
|
|
if (silent == false) {
|
|
PrintAndLogEx(INFO, "field is on");
|
|
}
|
|
}
|
|
|
|
if (continuous)
|
|
return PM3_SUCCESS;
|
|
else
|
|
return res;
|
|
}
|
|
|
|
static int CmdHF14AInfo(const char *Cmd) {
|
|
bool verbose = true;
|
|
bool do_nack_test = false;
|
|
bool do_aid_search = false;
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a info",
|
|
"This command makes more extensive tests against a ISO14443a tag in order to collect information",
|
|
"hf 14a info -nsv -> shows full information about the card\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0("n", "nacktest", "test for nack bug"),
|
|
arg_lit0("s", "aidsearch", "checks if AIDs from aidlist.json is present on the card and prints information about found AIDs"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
verbose = arg_get_lit(ctx, 1);
|
|
do_nack_test = arg_get_lit(ctx, 2);
|
|
do_aid_search = arg_get_lit(ctx, 3);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
infoHF14A(verbose, do_nack_test, do_aid_search);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// Collect ISO14443 Type A UIDs
|
|
static int CmdHF14ACUIDs(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a cuids",
|
|
"Collect n>0 ISO14443-a UIDs in one go",
|
|
"hf 14a cuids -n 5 --> Collect 5 UIDs");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0("n", "num", "<dec>", "Number of UIDs to collect"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
// requested number of UIDs
|
|
// collect at least 1 (e.g. if no parameter was given)
|
|
int n = arg_get_int_def(ctx, 1, 1);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
uint64_t t1 = msclock();
|
|
PrintAndLogEx(SUCCESS, "collecting %d UIDs", n);
|
|
|
|
// repeat n times
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "aborted via keyboard!\n");
|
|
break;
|
|
}
|
|
|
|
// execute anticollision procedure
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_RATS, 0, 0, NULL, 0);
|
|
PacketResponseNG resp;
|
|
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
iso14a_card_select_t *card = (iso14a_card_select_t *) resp.data.asBytes;
|
|
|
|
// check if command failed
|
|
if (resp.oldarg[0] == 0) {
|
|
PrintAndLogEx(WARNING, "card select failed.");
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "%s", sprint_hex_inrow(card->uid, card->uidlen));
|
|
}
|
|
}
|
|
PrintAndLogEx(SUCCESS, "end: %" PRIu64 " seconds", (msclock() - t1) / 1000);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ## simulate iso14443a tag
|
|
int CmdHF14ASim(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a sim",
|
|
"Simulate ISO/IEC 14443 type A tag with 4,7 or 10 byte UID\n"
|
|
"Use type 7 for Mifare Ultralight EV1, Amiibo (NTAG215 pack 0x8080)",
|
|
"hf 14a sim -t 1 --uid 11223344 -> MIFARE Classic 1k\n"
|
|
"hf 14a sim -t 2 -> MIFARE Ultralight\n"
|
|
"hf 14a sim -t 3 -> MIFARE Desfire\n"
|
|
"hf 14a sim -t 4 -> ISO/IEC 14443-4\n"
|
|
"hf 14a sim -t 5 -> MIFARE Tnp3xxx\n"
|
|
"hf 14a sim -t 6 -> MIFARE Mini\n"
|
|
"hf 14a sim -t 7 -> MFU EV1 / NTAG 215 Amiibo\n"
|
|
"hf 14a sim -t 8 -> MIFARE Classic 4k\n"
|
|
"hf 14a sim -t 9 -> FM11RF005SH Shanghai Metro\n"
|
|
"hf 14a sim -t 10 -> ST25TA IKEA Rothult\n"
|
|
"hf 14a sim -t 11 -> Javacard (JCOP)\n"
|
|
"hf 14a sim -t 12 -> 4K Seos card\n"
|
|
"hf 14a sim -t 13 -> MIFARE Ultralight C\n"
|
|
"hf 14a sim -t 14 -> MIFARE Ultralight AES\n"
|
|
"hf 14a sim -t 15 -> MIFARE Plus"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("t", "type", "<1-15> ", "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 have been read by reader. 0 = infinite"),
|
|
arg_lit0("x", NULL, "Performs the 'reader attack', nr/ar attack against a reader"),
|
|
arg_lit0(NULL, "sk", "Fill simulator keys from found keys"),
|
|
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_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int tagtype = arg_get_int_def(ctx, 1, 1);
|
|
|
|
int uid_len = 0;
|
|
uint8_t uid[10] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, uid, &uid_len);
|
|
|
|
uint16_t flags = 0;
|
|
bool useUIDfromEML = true;
|
|
|
|
if (uid_len > 0) {
|
|
FLAG_SET_UID_IN_DATA(flags, uid_len);
|
|
if (IS_FLAG_UID_IN_EMUL(flags)) {
|
|
PrintAndLogEx(ERR, "Please specify a 4, 7, or 10 byte UID");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
PrintAndLogEx(SUCCESS, "Emulating " _YELLOW_("ISO/IEC 14443 type A tag")" with " _GREEN_("%d byte UID (%s)"), uid_len, sprint_hex(uid, uid_len));
|
|
useUIDfromEML = false;
|
|
}
|
|
|
|
uint8_t exitAfterNReads = arg_get_int_def(ctx, 3, 0);
|
|
|
|
if (arg_get_lit(ctx, 4)) {
|
|
flags |= FLAG_NR_AR_ATTACK;
|
|
}
|
|
|
|
bool setEmulatorMem = arg_get_lit(ctx, 5);
|
|
bool verbose = arg_get_lit(ctx, 6);
|
|
|
|
int ulauth_1a1_len = 0;
|
|
int ulauth_1a2_len = 0;
|
|
uint8_t ulauth_1a1[16] = {0};
|
|
uint8_t ulauth_1a2[16] = {0};
|
|
CLIGetHexWithReturn(ctx, 7, ulauth_1a1, &ulauth_1a1_len);
|
|
CLIGetHexWithReturn(ctx, 8, ulauth_1a2, &ulauth_1a2_len);
|
|
CLIParserFree(ctx);
|
|
|
|
if (ulauth_1a1_len > 0) {
|
|
switch (tagtype) {
|
|
case 13: // ULC
|
|
if (ulauth_1a1_len != 8) {
|
|
PrintAndLogEx(ERR, "ULC --1a1 length must be 8 bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
break;
|
|
case 14: // ULAES
|
|
if (ulauth_1a1_len != 16) {
|
|
PrintAndLogEx(ERR, "ULAES --1a1 length must be 16 bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
break;
|
|
default:
|
|
PrintAndLogEx(ERR, "--1a1 option is only valid for tag types 13 (ULC) and 14 (ULAES)");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
if (ulauth_1a2_len > 0) {
|
|
switch (tagtype) {
|
|
case 13: // ULC
|
|
if (ulauth_1a2_len != 8) {
|
|
PrintAndLogEx(ERR, "ULC --1a2 length must be 8 bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
break;
|
|
case 14: // ULAES
|
|
if (ulauth_1a2_len != 16) {
|
|
PrintAndLogEx(ERR, "ULAES --1a2 length must be 16 bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
break;
|
|
default:
|
|
PrintAndLogEx(ERR, "--1a2 option is only valid for tag types 13 (ULC) and 14 (ULAES)");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (tagtype > 15) {
|
|
PrintAndLogEx(ERR, "Undefined tag %d", tagtype);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (useUIDfromEML) {
|
|
FLAG_SET_UID_IN_EMUL(flags);
|
|
}
|
|
|
|
struct {
|
|
uint8_t tagtype;
|
|
uint16_t flags;
|
|
uint8_t uid[10];
|
|
uint8_t exitAfter;
|
|
uint8_t rats[20];
|
|
uint8_t ulauth_1a1_len;
|
|
uint8_t ulauth_1a2_len;
|
|
uint8_t ulauth_1a1[16];
|
|
uint8_t ulauth_1a2[16];
|
|
} PACKED payload;
|
|
|
|
payload.tagtype = tagtype;
|
|
payload.flags = flags;
|
|
payload.exitAfter = exitAfterNReads;
|
|
payload.ulauth_1a1_len = ulauth_1a1_len;
|
|
payload.ulauth_1a2_len = ulauth_1a2_len;
|
|
memcpy(payload.uid, uid, uid_len);
|
|
memcpy(payload.ulauth_1a1, ulauth_1a1, ulauth_1a1_len);
|
|
memcpy(payload.ulauth_1a2, ulauth_1a2, ulauth_1a2_len);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ISO14443A_SIMULATE, (uint8_t *)&payload, sizeof(payload));
|
|
PacketResponseNG resp = {0};
|
|
|
|
sector_t *k_sector = NULL;
|
|
size_t k_sectors_cnt = MIFARE_4K_MAXSECTOR;
|
|
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort simulation");
|
|
bool keypress = kbd_enter_pressed();
|
|
while (keypress == false) {
|
|
keypress = kbd_enter_pressed();
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == false) {
|
|
continue;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) {
|
|
break;
|
|
}
|
|
|
|
const nonces_t *data = (nonces_t *)resp.data.asBytes;
|
|
readerAttack(k_sector, k_sectors_cnt, data[0], setEmulatorMem, verbose);
|
|
}
|
|
|
|
if (keypress) {
|
|
// inform device to break the sim loop since client has exited
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHF14ASniff(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a sniff",
|
|
"Sniff the communication between reader and tag\n"
|
|
"Use `hf 14a list` to view collected data.",
|
|
" hf 14a sniff -c -r"
|
|
);
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("c", "card", "triggered by first data from card"),
|
|
arg_lit0("r", "reader", "triggered by first 7-bit request from reader (REQ, WUP)"),
|
|
arg_lit0("i", "interactive", "Console will not be returned until sniff finishes or is aborted"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint8_t param = 0;
|
|
|
|
if (arg_get_lit(ctx, 1)) {
|
|
param |= 0x01;
|
|
}
|
|
|
|
if (arg_get_lit(ctx, 2)) {
|
|
param |= 0x02;
|
|
}
|
|
|
|
bool interactive = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ISO14443A_SNIFF, (uint8_t *)¶m, sizeof(uint8_t));
|
|
|
|
if (interactive) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort sniffing");
|
|
|
|
PacketResponseNG resp;
|
|
|
|
bool keypress = kbd_enter_pressed();
|
|
while (keypress == false) {
|
|
keypress = kbd_enter_pressed();
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ISO14443A_SNIFF, &resp, 500)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keypress) {
|
|
// inform device to break the sim loop since client has exited
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
WaitForResponse(CMD_HF_ISO14443A_SNIFF, &resp);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf 14a list")"` to view captured tracelog");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace save -h") "` to save tracelog for later analysing");
|
|
} else {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort sniffing");
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool silentMode) {
|
|
|
|
uint16_t cmdc = 0;
|
|
*dataoutlen = 0;
|
|
|
|
if (activateField) {
|
|
// select with no disconnect and set gs_frame_len
|
|
int selres = SelectCard14443A_4(false, !silentMode, NULL);
|
|
gs_frames_num = 0;
|
|
if (selres != PM3_SUCCESS) {
|
|
return selres;
|
|
}
|
|
}
|
|
|
|
if (leaveSignalON) {
|
|
cmdc |= ISO14A_NO_DISCONNECT;
|
|
}
|
|
|
|
uint8_t data[PM3_CMD_DATA_SIZE] = { 0x0a | gs_frames_num, 0x00 };
|
|
gs_frames_num ^= 1;
|
|
|
|
int min = MIN((PM3_CMD_DATA_SIZE - 2), (datainlen & 0x1FF));
|
|
memcpy(&data[2], datain, min);
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | cmdc, (datainlen & 0xFFFF) + 2, 0, data, min + 2);
|
|
|
|
uint8_t *recv;
|
|
PacketResponseNG resp;
|
|
resend:
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) {
|
|
recv = resp.data.asBytes;
|
|
int iLen = resp.oldarg[0];
|
|
|
|
if (iLen == 0) {
|
|
if (silentMode == false) {
|
|
PrintAndLogEx(ERR, "No card response");
|
|
}
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
*dataoutlen = iLen - 2;
|
|
if (*dataoutlen < 0) {
|
|
*dataoutlen = 0;
|
|
}
|
|
|
|
if (maxdataoutlen && *dataoutlen > maxdataoutlen) {
|
|
if (silentMode == false) {
|
|
PrintAndLogEx(ERR, "Buffer too small(%d). Needs %d bytes", *dataoutlen, maxdataoutlen);
|
|
}
|
|
return PM3_ELENGTH;
|
|
}
|
|
if (recv[0] & 0xf0) {
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
if (dbg_curr >= DBG_DEBUG) {
|
|
PrintAndLogEx(WARNING, "Card requested WTX");
|
|
}
|
|
// WARNING for users of this function:
|
|
// This NO_DISCONNECT flag will NOT save you. If you run your commands with LeaveSignalOn set to false, they will still fail if a card asks for WTX.
|
|
// Better keep the flag on and do a DropField(); at the end.
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_NO_DISCONNECT | cmdc, 5, 0, recv, min); // 2b frame +1b "data" + 2b crc. Hardcode length to 5 and reply to response
|
|
goto resend;
|
|
}
|
|
// OBS
|
|
// This if check below breaks command flow if the card for whatever reason decides to request a WTX.
|
|
// Because WTX will do a |= 0xF0 to the framing, this check triggers and everything that was happening will stop.
|
|
if (recv[0] != data[0]) {
|
|
if (silentMode == false) {
|
|
PrintAndLogEx(ERR, "iso14443-4 framing error. Card send %2x must be %2x", recv[0], data[0]);
|
|
}
|
|
return PM3_ELENGTH;
|
|
}
|
|
|
|
memcpy(dataout, &recv[2], *dataoutlen);
|
|
|
|
// CRC Check
|
|
if (iLen == -1) {
|
|
if (silentMode == false) {
|
|
PrintAndLogEx(ERR, "ISO 14443A CRC error");
|
|
}
|
|
return PM3_ECRC;
|
|
}
|
|
|
|
} else {
|
|
if (silentMode == false) {
|
|
PrintAndLogEx(ERR, "Reply timeout.");
|
|
}
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int SelectCard14443A_4_WithParameters(bool disconnect, bool verbose, iso14a_card_select_t *card, iso14a_polling_parameters_t *polling_parameters) {
|
|
// global vars should be prefixed with g_
|
|
gs_frame_len = 0;
|
|
gs_frames_num = 0;
|
|
|
|
if (card) {
|
|
memset(card, 0, sizeof(iso14a_card_select_t));
|
|
}
|
|
|
|
DropField();
|
|
|
|
// Anticollision + SELECT card
|
|
PacketResponseNG resp;
|
|
if (polling_parameters != NULL) {
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT | ISO14A_USE_CUSTOM_POLLING, 0, 0, (uint8_t *)polling_parameters, sizeof(iso14a_polling_parameters_t));
|
|
} else {
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
|
|
}
|
|
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// check result
|
|
if (resp.oldarg[0] == 0) {
|
|
if (verbose) {
|
|
PrintAndLogEx(WARNING, "No ISO14443-A Card in field");
|
|
}
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
if (resp.oldarg[0] != 1 && resp.oldarg[0] != 2) {
|
|
PrintAndLogEx(WARNING, "Card not in iso14443-4, res=%" PRId64 ".", resp.oldarg[0]);
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
iso14a_card_select_t *vcard = (iso14a_card_select_t *) resp.data.asBytes;
|
|
if (card) {
|
|
memcpy(card, vcard, sizeof(iso14a_card_select_t));
|
|
}
|
|
|
|
if (resp.oldarg[0] == 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
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, sizeof(rats), 0, rats, sizeof(rats));
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.oldarg[0] == 0) { // ats_len
|
|
if (verbose) {
|
|
PrintAndLogEx(FAILED, "Can't get ATS");
|
|
}
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
// get frame length from ATS in data field
|
|
if (resp.oldarg[0] > 1) {
|
|
uint8_t fsci = resp.data.asBytes[1] & 0x0f;
|
|
if (fsci < ARRAYLEN(atsFSC)) {
|
|
gs_frame_len = atsFSC[fsci];
|
|
}
|
|
}
|
|
|
|
if (card) {
|
|
card->ats_len = resp.oldarg[0];
|
|
memcpy(card->ats, resp.data.asBytes, card->ats_len);
|
|
}
|
|
} else {
|
|
// get frame length from ATS in card data structure
|
|
if (vcard->ats_len > 1) {
|
|
uint8_t fsci = vcard->ats[1] & 0x0f;
|
|
if (fsci < ARRAYLEN(atsFSC)) {
|
|
gs_frame_len = atsFSC[fsci];
|
|
}
|
|
}
|
|
}
|
|
|
|
SetISODEPState(ISODEP_NFCA);
|
|
|
|
if (disconnect) {
|
|
DropField();
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int SelectCard14443A_4(bool disconnect, bool verbose, iso14a_card_select_t *card) {
|
|
return SelectCard14443A_4_WithParameters(disconnect, verbose, card, NULL);
|
|
}
|
|
|
|
static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen, bool activateField, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool *chainingout) {
|
|
*chainingout = false;
|
|
|
|
size_t timeout = 1500;
|
|
if (activateField) {
|
|
// select with no disconnect and set gs_frame_len
|
|
iso14a_card_select_t card;
|
|
int selres = SelectCard14443A_4(false, true, &card);
|
|
if (selres != PM3_SUCCESS) {
|
|
return selres;
|
|
}
|
|
|
|
// Extract FWI and SFGI from ATS and increase timeout by the indicated values
|
|
// for most cards these values are trivially small so will make no practical
|
|
// difference but some "cards" like hf_cardhopper overwrite these to their
|
|
// maximum values resulting in ~5 seconds each which can cause timeouts if we
|
|
// just ignore it
|
|
if (((card.ats[1] & 0x20) == 0x20) && card.ats_len > 2) {
|
|
// TB is present in ATS
|
|
|
|
uint8_t tb;
|
|
if ((card.ats[1] & 0x10) == 0x10 && card.ats_len > 3) {
|
|
// TA is also present, so TB at ats[3]
|
|
tb = card.ats[3];
|
|
} else {
|
|
// TA is not present, so TB is at ats[2]
|
|
tb = card.ats[2];
|
|
}
|
|
|
|
uint8_t fwi = (tb & 0xF0) >> 4;
|
|
if (fwi != 0x0F) {
|
|
uint32_t fwt = 256 * 16 * (1 << fwi);
|
|
timeout += fwt;
|
|
}
|
|
|
|
uint8_t sfgi = tb & 0x0F;
|
|
if (sfgi != 0x0F) {
|
|
uint32_t sgft = 256 * 16 * (1 << sfgi);
|
|
timeout += sgft;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t cmdc = (ISO14A_APDU | ISO14A_NO_DISCONNECT);
|
|
if (chainingin) {
|
|
cmdc |= ISO14A_SEND_CHAINING;
|
|
}
|
|
|
|
// "Command APDU" length should be 5+255+1, but javacard's APDU buffer might be smaller - 133 bytes
|
|
// https://stackoverflow.com/questions/32994936/safe-max-java-card-apdu-data-command-and-respond-size
|
|
// here length PM3_CMD_DATA_SIZE=512
|
|
// timeout must be authomatically set by "get ATS"
|
|
if (datain) {
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, cmdc, (datainlen & 0x1FF), 0, datain, (datainlen & 0x1FF));
|
|
} else {
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, cmdc, 0, 0, NULL, 0);
|
|
}
|
|
|
|
PacketResponseNG resp;
|
|
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, timeout) == false) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: Reply timeout");
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
const uint8_t *recv = resp.data.asBytes;
|
|
int iLen = resp.oldarg[0];
|
|
uint8_t res = resp.oldarg[1];
|
|
|
|
int dlen = iLen - 2;
|
|
if (dlen < 0) {
|
|
dlen = 0;
|
|
}
|
|
*dataoutlen += dlen;
|
|
|
|
if (maxdataoutlen && *dataoutlen > maxdataoutlen) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: Buffer too small(%d), needs %d bytes", *dataoutlen, maxdataoutlen);
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
// I-block ACK
|
|
if ((res & 0xF2) == 0xA2) {
|
|
*dataoutlen = 0;
|
|
*chainingout = true;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (iLen == 0) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: No APDU response");
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
// check apdu length
|
|
if (iLen < 2 && iLen >= 0) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: Small APDU response, len %d", iLen);
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
// check block TODO
|
|
if (iLen == -2) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: Block type mismatch");
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
memcpy(dataout, recv, dlen);
|
|
|
|
// chaining
|
|
if ((res & 0x10) != 0) {
|
|
*chainingout = true;
|
|
}
|
|
|
|
// CRC Check
|
|
if (iLen == -1) {
|
|
PrintAndLogEx(DEBUG, "ERR: APDU: ISO 14443A CRC error");
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
|
|
// Button pressed / user cancelled
|
|
if (iLen == -3) {
|
|
PrintAndLogEx(DEBUG, "\naborted via keyboard!");
|
|
return PM3_EAPDU_FAIL;
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int ExchangeAPDU14a(const uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) {
|
|
*dataoutlen = 0;
|
|
bool chaining = false;
|
|
int res;
|
|
|
|
// 3 byte here - 1b framing header, 2b crc16
|
|
if (g_apdu_in_framing_enable &&
|
|
((gs_frame_len && (datainlen > gs_frame_len - 3)) || (datainlen > PM3_CMD_DATA_SIZE - 3))) {
|
|
|
|
int clen = 0;
|
|
|
|
bool vActivateField = activateField;
|
|
|
|
do {
|
|
int vlen = MIN(gs_frame_len - 3, datainlen - clen);
|
|
bool chainBlockNotLast = ((clen + vlen) < datainlen);
|
|
|
|
*dataoutlen = 0;
|
|
res = CmdExchangeAPDU(chainBlockNotLast, &datain[clen], vlen, vActivateField, dataout, maxdataoutlen, dataoutlen, &chaining);
|
|
if (res != PM3_SUCCESS) {
|
|
if (leaveSignalON == false) {
|
|
DropField();
|
|
}
|
|
return 200;
|
|
}
|
|
|
|
// check R-block ACK
|
|
// TODO check this one...
|
|
if ((*dataoutlen == 0) && (chaining != chainBlockNotLast)) {
|
|
if (leaveSignalON == false) {
|
|
DropField();
|
|
}
|
|
return 201;
|
|
}
|
|
|
|
clen += vlen;
|
|
vActivateField = false;
|
|
if (*dataoutlen) {
|
|
if (clen != datainlen) {
|
|
PrintAndLogEx(ERR, "APDU: I-block/R-block sequence error. Data len=%d, Sent=%d, Last packet len=%d", datainlen, clen, *dataoutlen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
} while (clen < datainlen);
|
|
|
|
} else {
|
|
res = CmdExchangeAPDU(false, datain, datainlen, activateField, dataout, maxdataoutlen, dataoutlen, &chaining);
|
|
if (res != PM3_SUCCESS) {
|
|
if (leaveSignalON == false) {
|
|
DropField();
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
while (chaining) {
|
|
// I-block with chaining
|
|
res = CmdExchangeAPDU(false, NULL, 0, false, &dataout[*dataoutlen], maxdataoutlen, dataoutlen, &chaining);
|
|
if (res != PM3_SUCCESS) {
|
|
if (leaveSignalON == false) {
|
|
DropField();
|
|
}
|
|
return 100;
|
|
}
|
|
}
|
|
|
|
if (leaveSignalON == false) {
|
|
DropField();
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ISO14443-4. 7. Half-duplex block transmission protocol
|
|
static int CmdHF14AAPDU(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a apdu",
|
|
"Sends an ISO 7816-4 APDU via ISO 14443-4 block transmission protocol (T=CL).\n"
|
|
"Works with all APDU types from ISO 7816-4:2013\n"
|
|
"\n"
|
|
"note:\n"
|
|
" `-m` and `-d` goes hand in hand\n"
|
|
" -m <CLA INS P1 P2> -d 325041592E5359532E4444463031\n"
|
|
"\n"
|
|
" OR\n"
|
|
"\n"
|
|
" use `-d` with complete APDU data\n"
|
|
" -d 00A404000E325041592E5359532E444446303100",
|
|
"hf 14a apdu -st -d 00A404000E325041592E5359532E444446303100\n"
|
|
"hf 14a apdu -sd -d 00A404000E325041592E5359532E444446303100 -> decode apdu\n"
|
|
"hf 14a apdu -sm 00A40400 -d 325041592E5359532E4444463031 -l 256 -> encode standard apdu\n"
|
|
"hf 14a apdu -sm 00A40400 -d 325041592E5359532E4444463031 -el 65536 -> encode extended apdu\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("s", "select", "activate field and select card"),
|
|
arg_lit0("k", "keep", "keep signal field ON after receive"),
|
|
arg_lit0("t", "tlv", "decode TLV"),
|
|
arg_lit0(NULL, "decode", "decode APDU request"),
|
|
arg_str0("m", "make", "<hex>", "APDU header, 4 bytes <CLA INS P1 P2>"),
|
|
arg_lit0("e", "extended", "make extended length apdu if `m` parameter included"),
|
|
arg_int0("l", "le", "<dec>", "Le APDU parameter if `m` parameter included"),
|
|
arg_strx1("d", "data", "<hex>", "full APDU package or data if `m` parameter included"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
bool activateField = arg_get_lit(ctx, 1);
|
|
bool leaveSignalON = arg_get_lit(ctx, 2);
|
|
bool decodeTLV = arg_get_lit(ctx, 3);
|
|
bool decodeAPDU = arg_get_lit(ctx, 4);
|
|
|
|
uint8_t header[PM3_CMD_DATA_SIZE];
|
|
int headerlen = 0;
|
|
CLIGetHexWithReturn(ctx, 5, header, &headerlen);
|
|
|
|
bool makeAPDU = (headerlen > 0);
|
|
|
|
if (makeAPDU && headerlen != 4) {
|
|
PrintAndLogEx(ERR, "header length must be 4 bytes instead of %d", headerlen);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool extendedAPDU = arg_get_lit(ctx, 6);
|
|
int le = arg_get_int_def(ctx, 7, 0);
|
|
|
|
uint8_t data[PM3_CMD_DATA_SIZE];
|
|
int datalen = 0;
|
|
|
|
if (makeAPDU) {
|
|
uint8_t apdudata[PM3_CMD_DATA_SIZE] = {0};
|
|
int apdudatalen = 0;
|
|
|
|
CLIGetHexBLessWithReturn(ctx, 8, apdudata, &apdudatalen, 1 + 2);
|
|
|
|
APDU_t apdu;
|
|
apdu.cla = header[0];
|
|
apdu.ins = header[1];
|
|
apdu.p1 = header[2];
|
|
apdu.p2 = header[3];
|
|
|
|
apdu.lc = apdudatalen;
|
|
apdu.data = apdudata;
|
|
|
|
apdu.extended_apdu = extendedAPDU;
|
|
apdu.le = le;
|
|
|
|
if (APDUEncode(&apdu, data, &datalen)) {
|
|
PrintAndLogEx(ERR, "can't make apdu with provided parameters.");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
} else {
|
|
if (extendedAPDU) {
|
|
PrintAndLogEx(ERR, "make mode not set but here `e` option.");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (le > 0) {
|
|
PrintAndLogEx(ERR, "make mode not set but here `l` option.");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// len = data + PCB(1b) + CRC(2b)
|
|
CLIGetHexBLessWithReturn(ctx, 8, data, &datalen, 1 + 2);
|
|
}
|
|
CLIParserFree(ctx);
|
|
|
|
PrintAndLogEx(SUCCESS, _YELLOW_("%s%s%s"),
|
|
activateField ? "select card" : "",
|
|
leaveSignalON ? ", keep field on" : "",
|
|
decodeTLV ? ", TLV" : ""
|
|
);
|
|
PrintAndLogEx(SUCCESS, ">>> %s", sprint_hex_inrow(data, datalen));
|
|
|
|
if (decodeAPDU) {
|
|
APDU_t apdu;
|
|
|
|
if (APDUDecode(data, datalen, &apdu) == 0)
|
|
APDUPrint(apdu);
|
|
else
|
|
PrintAndLogEx(WARNING, "can't decode APDU.");
|
|
}
|
|
|
|
int res = ExchangeAPDU14a(data, datalen, activateField, leaveSignalON, data, PM3_CMD_DATA_SIZE, &datalen);
|
|
if (res != PM3_SUCCESS)
|
|
return res;
|
|
|
|
PrintAndLogEx(SUCCESS, "<<< %s | %s", sprint_hex_inrow(data, datalen), sprint_ascii(data, datalen));
|
|
PrintAndLogEx(SUCCESS, "<<< status: %02X %02X - %s", data[datalen - 2], data[datalen - 1], GetAPDUCodeDescription(data[datalen - 2], data[datalen - 1]));
|
|
|
|
// TLV decoder
|
|
if (decodeTLV && datalen > 4) {
|
|
TLVPrintFromBuffer(data, datalen - 2);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14ACmdRaw(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a raw",
|
|
"Sends raw bytes over ISO14443a. With option to use TOPAZ 14a mode.",
|
|
"hf 14a raw -sc 3000 -> select, crc, where 3000 == 'read block 00'\n"
|
|
"hf 14a raw -ak -b 7 40 -> send 7 bit byte 0x40\n"
|
|
"Crypto1 session example, with special auth shortcut 6xxx<key>:\n"
|
|
"hf 14a raw --crypto1 -skc 6000FFFFFFFFFFFF\n"
|
|
"hf 14a raw --crypto1 -kc 3000\n"
|
|
"hf 14a raw --crypto1 -kc 6007FFFFFFFFFFFF\n"
|
|
"hf 14a raw --crypto1 -c 3007"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("a", NULL, "Active signal field ON without select"),
|
|
arg_lit0("c", NULL, "Calculate and append CRC"),
|
|
arg_lit0("k", NULL, "Keep signal field ON after receive"),
|
|
arg_lit0("3", NULL, "ISO14443-3 select only (skip RATS)"),
|
|
arg_lit0("r", NULL, "Do not read response"),
|
|
arg_lit0("s", NULL, "Active signal field ON with select"),
|
|
arg_int0("t", "timeout", "<ms>", "Timeout in milliseconds"),
|
|
arg_int0("b", NULL, "<dec>", "Number of bits to send. Useful for send partial byte"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_int0("w", "wait", "<us>", "Wait in microseconds between select and command"),
|
|
arg_lit0(NULL, "topaz", "Use Topaz protocol to send command"),
|
|
arg_lit0(NULL, "crypto1", "Use crypto1 session"),
|
|
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
|
arg_strx1(NULL, NULL, "<hex>", "Raw bytes to send"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
bool active = arg_get_lit(ctx, 1);
|
|
bool crc = arg_get_lit(ctx, 2);
|
|
bool keep_field_on = arg_get_lit(ctx, 3);
|
|
bool no_rats = arg_get_lit(ctx, 4);
|
|
bool reply = (arg_get_lit(ctx, 5) == false);
|
|
bool active_select = arg_get_lit(ctx, 6);
|
|
uint32_t timeout = (uint32_t)arg_get_int_def(ctx, 7, 0);
|
|
uint16_t numbits = (uint16_t)arg_get_int_def(ctx, 8, 0);
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
uint32_t wait_us = (uint32_t)arg_get_int_def(ctx, 10, 0);
|
|
bool topazmode = arg_get_lit(ctx, 11);
|
|
bool crypto1mode = arg_get_lit(ctx, 12);
|
|
bool use_schann = arg_get_lit(ctx, 13);
|
|
|
|
int datalen = 0;
|
|
uint8_t data[PM3_CMD_DATA_SIZE_MIX] = {0};
|
|
CLIGetHexWithReturn(ctx, 14, data, &datalen);
|
|
CLIParserFree(ctx);
|
|
|
|
bool bTimeout = (timeout) ? true : false;
|
|
|
|
// ensure we can add 2byte crc to input data
|
|
if (datalen >= sizeof(data) + 2) {
|
|
if (crc) {
|
|
PrintAndLogEx(FAILED, "Buffer is full, we can't add CRC to your data");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
uint32_t flags = 0;
|
|
|
|
if (use_schann) {
|
|
flags |= ISO14A_APPEND_CMAC;
|
|
|
|
// Can't precalculate crc client side since we are adding cmac on device side.
|
|
if (crc) {
|
|
flags |= ISO14A_APPEND_CRC;
|
|
crc = false;
|
|
}
|
|
}
|
|
|
|
if (crc && datalen > 0 && datalen < sizeof(data) - 2) {
|
|
uint8_t first, second;
|
|
if (topazmode) {
|
|
compute_crc(CRC_14443_B, data, datalen, &first, &second);
|
|
} else {
|
|
compute_crc(CRC_14443_A, data, datalen, &first, &second);
|
|
}
|
|
data[datalen++] = first;
|
|
data[datalen++] = second;
|
|
}
|
|
|
|
if (active || active_select) {
|
|
flags |= ISO14A_CONNECT;
|
|
flags |= ISO14A_CLEARTRACE;
|
|
if (active)
|
|
flags |= ISO14A_NO_SELECT;
|
|
}
|
|
|
|
// 32b MSB encode wait_us, 32b LSB encode timeout
|
|
uint64_t argtimeout = 0;
|
|
if (wait_us) {
|
|
flags |= ISO14A_SET_WAIT_US;
|
|
argtimeout = ((uint64_t)wait_us) << 32;
|
|
}
|
|
|
|
if (bTimeout) {
|
|
#define MAX_TIMEOUT 40542464 // = (2^32-1) * (8*16) / 13560000Hz * 1000ms/s
|
|
flags |= ISO14A_SET_TIMEOUT;
|
|
if (timeout > MAX_TIMEOUT) {
|
|
timeout = MAX_TIMEOUT;
|
|
PrintAndLogEx(INFO, "Set timeout to 40542 seconds (11.26 hours). The max we can wait for response");
|
|
}
|
|
argtimeout |= 13560000 / 1000 / (8 * 16) * timeout; // timeout in ETUs (time to transfer 1 bit, approx. 9.4 us)
|
|
}
|
|
|
|
if (keep_field_on) {
|
|
flags |= ISO14A_NO_DISCONNECT;
|
|
}
|
|
|
|
if (datalen > 0) {
|
|
flags |= ISO14A_RAW;
|
|
}
|
|
|
|
if (topazmode) {
|
|
flags |= ISO14A_TOPAZMODE;
|
|
}
|
|
|
|
if (crypto1mode) {
|
|
flags |= ISO14A_CRYPTO1MODE;
|
|
if (numbits > 0 || topazmode) {
|
|
PrintAndLogEx(FAILED, "crypto1 mode cannot be used with other modes or partial bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (no_rats) {
|
|
flags |= ISO14A_NO_RATS;
|
|
}
|
|
|
|
// Max buffer is PM3_CMD_DATA_SIZE_MIX
|
|
datalen = (datalen > PM3_CMD_DATA_SIZE_MIX) ? PM3_CMD_DATA_SIZE_MIX : datalen;
|
|
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, flags, (datalen & 0x1FF) | ((uint32_t)(numbits << 16)), argtimeout, data, datalen);
|
|
|
|
if (reply) {
|
|
int res = 0;
|
|
if (active_select)
|
|
res = waitCmd(true, timeout, verbose);
|
|
if (res == PM3_SUCCESS && datalen > 0)
|
|
waitCmd(false, timeout, verbose);
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int waitCmd(bool i_select, uint32_t timeout, bool verbose) {
|
|
PacketResponseNG resp;
|
|
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, timeout + 1500)) {
|
|
uint16_t len = (resp.oldarg[0] & 0xFFFF);
|
|
if (i_select) {
|
|
len = (resp.oldarg[1] & 0xFFFF);
|
|
if (len) {
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "Card selected. UID[%u]:", len);
|
|
} else {
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Can't select card.");
|
|
}
|
|
} else {
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "received " _YELLOW_("%u") " bytes", len);
|
|
}
|
|
}
|
|
|
|
if (len == 0) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t *data = resp.data.asBytes;
|
|
|
|
if (i_select == false && len >= 3) {
|
|
bool crc = check_crc(CRC_14443_A, data, len);
|
|
|
|
char s[16];
|
|
snprintf(s,
|
|
sizeof(s),
|
|
(crc) ? _GREEN_("%02X %02X") : _RED_("%02X %02X"),
|
|
data[len - 2],
|
|
data[len - 1]
|
|
);
|
|
|
|
PrintAndLogEx(SUCCESS, "%s[ %s ]", sprint_hex(data, len - 2), s);
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "%s", sprint_hex(data, len));
|
|
}
|
|
|
|
} else {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AAntiFuzz(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a antifuzz",
|
|
"Tries to fuzz the ISO14443a anticollision phase",
|
|
"hf 14a antifuzz -4\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("4", NULL, "4 byte uid"),
|
|
arg_lit0("7", NULL, "7 byte uid"),
|
|
arg_lit0(NULL, "10", "10 byte uid"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
struct {
|
|
uint8_t flag;
|
|
} PACKED param;
|
|
param.flag = 0;
|
|
FLAG_SET_UID_IN_DATA(param.flag, 4);
|
|
if (arg_get_lit(ctx, 2)) {
|
|
FLAG_SET_UID_IN_DATA(param.flag, 7);
|
|
}
|
|
if (arg_get_lit(ctx, 3)) {
|
|
FLAG_SET_UID_IN_DATA(param.flag, 10);
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ISO14443A_ANTIFUZZ, (uint8_t *)¶m, sizeof(param));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHF14AChaining(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a chaining",
|
|
"Enable/Disable ISO14443a input chaining. Maximum input length goes from ATS.",
|
|
"hf 14a chaining -> show chaining enable/disable state\n"
|
|
"hf 14a chaining --off -> disable chaining\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("1", "on", "enabled chaining"),
|
|
arg_lit0("0", "off", "disable chaining"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool on = arg_get_lit(ctx, 1);
|
|
bool off = arg_get_lit(ctx, 2);
|
|
CLIParserFree(ctx);
|
|
|
|
if ((on + off) > 1) {
|
|
PrintAndLogEx(INFO, "Select only one option");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (on)
|
|
Set_apdu_in_framing(true);
|
|
|
|
if (off)
|
|
Set_apdu_in_framing(false);
|
|
|
|
PrintAndLogEx(INFO, "\nISO 14443-4 input chaining %s.\n", g_apdu_in_framing_enable ? "enabled" : "disabled");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void printTag(const char *tag) {
|
|
PrintAndLogEx(SUCCESS, " " _YELLOW_("%s"), tag);
|
|
}
|
|
|
|
// Based on NXP AN10833 Rev 3.8 and NXP AN10834 Rev 4.2
|
|
int detect_nxp_card(uint8_t sak, uint16_t atqa, uint64_t select_status,
|
|
uint8_t ats_hist_len, uint8_t *ats_hist,
|
|
bool version_hw_available, version_hw_t *version_hw) {
|
|
int type = MTNONE;
|
|
|
|
if (version_hw_available) {
|
|
|
|
switch (version_hw->product_type & 0x0F) {
|
|
case 0x1: {
|
|
type |= MTDESFIRE;
|
|
|
|
// special cases, override major_product_version_str when needed
|
|
switch (version_hw->major_product_version) {
|
|
case 0x42:
|
|
type |= MTEMV;
|
|
break;
|
|
case 0xA0:
|
|
type |= MTDUOX;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x2: {
|
|
type |= MTPLUS;
|
|
break;
|
|
}
|
|
case 0x3: {
|
|
type |= MTULTRALIGHT;
|
|
break;
|
|
}
|
|
case 0x4: {
|
|
type |= MTNTAG;
|
|
break;
|
|
}
|
|
case 0x7: {
|
|
type |= MTNTAG;
|
|
break;
|
|
}
|
|
case 0x8: {
|
|
type |= MTDESFIRE;
|
|
break;
|
|
}
|
|
case 0x9: {
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ((sak & 0x44) == 0x40) {
|
|
// ISO18092 Table 15: Target compliant with NFC transport protocol
|
|
type |= MTISO18092;
|
|
}
|
|
|
|
if ((sak & 0x02) == 0x00) { // SAK b2=0
|
|
|
|
if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1
|
|
|
|
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=1
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19
|
|
type |= MTCLASSIC;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38
|
|
type |= MTCLASSIC;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0
|
|
|
|
if (select_status == 4) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS GetVersion
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
type |= MTPLUS;
|
|
} else {
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09
|
|
type |= MTMINI;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28
|
|
type |= MTCLASSIC;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0
|
|
|
|
if (select_status == 4) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS GetVersion
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
type |= MTPLUS;
|
|
|
|
} else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) {
|
|
type |= MTPLUS;
|
|
|
|
} else {
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 no_ATS, SAK=0x08
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=0
|
|
|
|
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1
|
|
|
|
// if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11
|
|
// } else { // SAK b2=0 b4=0 b5=1 b1=0, SAK=0x10
|
|
// }
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1
|
|
type |= MTCLASSIC;
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1, SAK=0x20
|
|
|
|
if (select_status == 1) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion
|
|
|
|
if ((version_hw->product_type & 0x7F) == 0x02) {
|
|
type |= MTPLUS;
|
|
|
|
} else if (((version_hw->product_type & 0x7F) == 0x01) ||
|
|
(version_hw->product_type == 0x08) ||
|
|
(version_hw->product_type == 0x91)) {
|
|
type |= MTDESFIRE;
|
|
|
|
} else if (version_hw->product_type == 0x04) {
|
|
type |= (MTDESFIRE | MT424);
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
type |= MTPLUS;
|
|
|
|
} else {
|
|
|
|
if ((atqa == 0x0001) || (atqa == 0x0004)) {
|
|
type |= HID_SEOS;
|
|
}
|
|
|
|
if (atqa == 0x0004) {
|
|
type |= MTEMV;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0, SAK=0x00
|
|
|
|
if (version_hw_available == false) {
|
|
// SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion
|
|
int status = mfuc_test_authentication_support();
|
|
if (status == PM3_SUCCESS) {
|
|
type |= MTULTRALIGHT_C;
|
|
}
|
|
}
|
|
type |= MTULTRALIGHT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else { // SAK b2=1
|
|
|
|
if (sak == 0x0A) {
|
|
|
|
if (atqa == 0x0003) {
|
|
// Uses Shanghai algo
|
|
type |= MTFUDAN;
|
|
|
|
} else if (atqa == 0x0005) {
|
|
type |= MTFUDAN;
|
|
}
|
|
|
|
} else if (sak == 0x53) {
|
|
type |= MTFUDAN;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
// Based on NXP AN10833 Rev 3.8 and NXP AN10834 Rev 4.2
|
|
static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_status,
|
|
uint8_t ats_hist_len, uint8_t *ats_hist,
|
|
bool version_hw_available, version_hw_t *version_hw) {
|
|
int type = MTNONE;
|
|
const char *product_type_str = "";
|
|
const char *major_product_version_str = "";
|
|
const char *storage_size_str = "";
|
|
|
|
if (version_hw_available) {
|
|
|
|
switch (version_hw->product_type & 0x0F) {
|
|
case 0x1: {
|
|
product_type_str = "MIFARE DESFire";
|
|
// special cases, override product_type_str when needed
|
|
if (version_hw->product_type == 0x91) {
|
|
product_type_str = "Apple Wallet DESFire Applet";
|
|
}
|
|
|
|
// general rule
|
|
switch (version_hw->major_product_version & 0x0F) {
|
|
case 0x01:
|
|
major_product_version_str = "EV1";
|
|
break;
|
|
case 0x02:
|
|
major_product_version_str = "EV2";
|
|
break;
|
|
case 0x03:
|
|
major_product_version_str = "EV3";
|
|
break;
|
|
}
|
|
|
|
// special cases, override major_product_version_str when needed
|
|
switch (version_hw->major_product_version) {
|
|
case 0x00:
|
|
major_product_version_str = "MF3ICD40";
|
|
break;
|
|
case 0x42:
|
|
major_product_version_str = "EV2 + EMV";
|
|
break;
|
|
case 0xA0:
|
|
product_type_str = "MIFARE DUOX";
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x2: {
|
|
product_type_str = "MIFARE Plus";
|
|
switch (version_hw->major_product_version) {
|
|
case 0x11:
|
|
major_product_version_str = "EV1";
|
|
break;
|
|
case 0x22:
|
|
major_product_version_str = "EV2";
|
|
break;
|
|
default:
|
|
major_product_version_str = "n/a";
|
|
}
|
|
break;
|
|
}
|
|
case 0x3: {
|
|
product_type_str = "MIFARE Ultralight";
|
|
switch (version_hw->major_product_version) {
|
|
case 0x01: {
|
|
major_product_version_str = "EV1";
|
|
|
|
if (version_hw->storage_size == 0x0B) {
|
|
storage_size_str = "48b";
|
|
} else if (version_hw->storage_size == 0x0E) {
|
|
storage_size_str = "128b";
|
|
}
|
|
break;
|
|
}
|
|
case 0x02:
|
|
major_product_version_str = "Nano";
|
|
break;
|
|
case 0x04:
|
|
major_product_version_str = "AES";
|
|
break;
|
|
default:
|
|
major_product_version_str = "n/a";
|
|
}
|
|
break;
|
|
}
|
|
case 0x4: {
|
|
product_type_str = "NTAG";
|
|
switch (version_hw->major_product_version) {
|
|
case 0x01:
|
|
major_product_version_str = "2xx";
|
|
break;
|
|
case 0x02:
|
|
major_product_version_str = "210µ";
|
|
break;
|
|
case 0x03:
|
|
major_product_version_str = "213 TT";
|
|
break;
|
|
case 0x10:
|
|
// Not sure about its product type = 4
|
|
major_product_version_str = "413 DNA";
|
|
break;
|
|
case 0x30:
|
|
major_product_version_str = "4xx";
|
|
break;
|
|
default:
|
|
major_product_version_str = "n/a";
|
|
}
|
|
break;
|
|
}
|
|
case 0x7: {
|
|
product_type_str = "NTAG I2C";
|
|
break;
|
|
}
|
|
case 0x8: {
|
|
product_type_str = "MIFARE DESFire Light";
|
|
break;
|
|
}
|
|
case 0x9: {
|
|
product_type_str = "MIFARE Hospitality";
|
|
switch (version_hw->major_product_version) {
|
|
case 0x01:
|
|
major_product_version_str = "AES";
|
|
break;
|
|
default:
|
|
major_product_version_str = "n/a";
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
product_type_str = "Unknown NXP tag";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (storage_size_str == NULL) {
|
|
static char size_str[16];
|
|
uint16_t usize = 1 << ((version_hw->storage_size >> 1) + 1);
|
|
uint16_t lsize = 1 << (version_hw->storage_size >> 1);
|
|
|
|
// is LSB set?
|
|
if ((version_hw->storage_size & 0x01) == 1) {
|
|
|
|
// if set, its a range between upper size and lower size
|
|
|
|
if (lsize < 1024) {
|
|
snprintf(size_str, sizeof(size_str), "%u - %uB", usize, lsize);
|
|
} else {
|
|
snprintf(size_str, sizeof(size_str), "%d - %dK", (usize / 1024), (lsize / 1024));
|
|
}
|
|
|
|
} else {
|
|
|
|
// if not set, it's lower size
|
|
if (lsize < 1024) {
|
|
snprintf(size_str, sizeof(size_str), "%uB", lsize);
|
|
} else {
|
|
snprintf(size_str, sizeof(size_str), "%dK", (lsize / 1024));
|
|
}
|
|
}
|
|
|
|
storage_size_str = size_str;
|
|
|
|
}
|
|
}
|
|
|
|
char tag_info[128];
|
|
|
|
if ((sak & 0x44) == 0x40) {
|
|
// ISO18092 Table 15: Target compliant with NFC transport protocol
|
|
if ((atqa & 0x0110) == 0x0110) {
|
|
printTag("P2P Support / Proprietary");
|
|
} else {
|
|
printTag("P2P Support / Android");
|
|
}
|
|
type |= MTISO18092;
|
|
}
|
|
|
|
if ((sak & 0x02) == 0x00) { // SAK b2=0
|
|
|
|
if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1
|
|
|
|
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=1
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19
|
|
printTag("MIFARE Classic 2K");
|
|
type |= MTCLASSIC;
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38
|
|
printTag("SmartMX with MIFARE Classic 4K");
|
|
type |= MTCLASSIC;
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0
|
|
|
|
if (select_status == 4) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS GetVersion
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
|
|
printTag(tag_info);
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
|
|
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
|
printTag("MIFARE Plus S 4K in SL1");
|
|
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
|
printTag("MIFARE Plus X 4K in SL1");
|
|
} else {
|
|
printTag("Unrecognized MIFARE Plus??");
|
|
}
|
|
type |= MTPLUS;
|
|
} else {
|
|
if ((atqa & 0x0040) == 0x0040) {
|
|
printTag("MIFARE Classic 4K CL2 with ATS!");
|
|
} else {
|
|
printTag("MIFARE Classic 4K with ATS!");
|
|
}
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18
|
|
|
|
if ((atqa & 0x0040) == 0x0040) {
|
|
printTag("MIFARE Classic 4K CL2");
|
|
} else {
|
|
printTag("MIFARE Classic 4K");
|
|
}
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09
|
|
|
|
if ((atqa & 0x0040) == 0x0040) {
|
|
printTag("MIFARE Mini 0.3K CL2");
|
|
} else {
|
|
printTag("MIFARE Mini 0.3K");
|
|
}
|
|
type |= MTMINI;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28
|
|
printTag("SmartMX with MIFARE Classic 1K");
|
|
printTag("FM1208-10 with MIFARE Classic 1K");
|
|
printTag("FM1216-137 with MIFARE Classic 1K");
|
|
type |= MTCLASSIC;
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0
|
|
|
|
if (select_status == 4) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS GetVersion
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
|
|
printTag(tag_info);
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
|
|
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
|
printTag("MIFARE Plus S 2K in SL1");
|
|
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
|
printTag("MIFARE Plus X 2K in SL1");
|
|
} else {
|
|
printTag("Unrecognized MIFARE Plus??");
|
|
}
|
|
type |= MTPLUS;
|
|
|
|
} else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) {
|
|
|
|
if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
|
|
printTag("MIFARE Plus SE 1K 17pF");
|
|
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
|
|
printTag("MIFARE Plus SE 1K 70pF");
|
|
} else {
|
|
printTag("Unrecognized MIFARE Plus SE??");
|
|
}
|
|
type |= MTPLUS;
|
|
|
|
} else {
|
|
|
|
if ((atqa & 0x0040) == 0x0040) {
|
|
printTag("MIFARE Classic 1K CL2 with ATS!");
|
|
} else {
|
|
printTag("MIFARE Classic 1K with ATS!");
|
|
}
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 no_ATS, SAK=0x08
|
|
if ((atqa & 0x0040) == 0x0040) {
|
|
printTag("MIFARE Classic 1K CL2");
|
|
} else {
|
|
printTag("MIFARE Classic 1K");
|
|
}
|
|
type |= MTCLASSIC;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=0
|
|
|
|
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11
|
|
printTag("MIFARE Plus 4K in SL2");
|
|
} else { // SAK b2=0 b4=0 b5=1 b1=0, SAK=0x10
|
|
printTag("MIFARE Plus 2K in SL2");
|
|
}
|
|
type |= MTPLUS;
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0
|
|
|
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1
|
|
printTag("TNP3xxx (TagNPlay, Activision Game Appliance)");
|
|
type |= MTCLASSIC;
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0
|
|
|
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1, SAK=0x20
|
|
|
|
if (select_status == 1) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion
|
|
|
|
if ((version_hw->product_type & 0x7F) == 0x02) {
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL0/SL3", product_type_str, major_product_version_str, storage_size_str);
|
|
type |= MTPLUS;
|
|
|
|
} else if (((version_hw->product_type & 0x7F) == 0x01) ||
|
|
(version_hw->product_type == 0x08) ||
|
|
(version_hw->product_type == 0x91)) {
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
|
type |= MTDESFIRE;
|
|
|
|
} else if (version_hw->product_type == 0x04) {
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
|
type |= (MTDESFIRE | MT424);
|
|
|
|
} else {
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
|
}
|
|
printTag(tag_info);
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion
|
|
|
|
if (ats_hist_len > 0) {
|
|
|
|
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
|
|
|
|
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
|
|
|
if ((atqa & 0xFF0F) == 0x0004) {
|
|
printTag("MIFARE Plus S 2K in SL0/SL3");
|
|
} else if ((atqa & 0xFF0F) == 0x0002) {
|
|
printTag("MIFARE Plus S 4K in SL0/SL3");
|
|
} else {
|
|
printTag("Unrecognized MIFARE Plus??");
|
|
}
|
|
|
|
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
|
printTag("MIFARE Plus X 2K/4K in SL0/SL3");
|
|
|
|
} else if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
|
|
printTag("MIFARE Plus SE 1K 17pF");
|
|
|
|
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
|
|
printTag("MIFARE Plus SE 1K 70pF");
|
|
|
|
} else {
|
|
printTag("Unknown MIFARE Plus");
|
|
}
|
|
type |= MTPLUS;
|
|
|
|
} else {
|
|
|
|
if ((atqa == 0x0001) || (atqa == 0x0004)) {
|
|
printTag("HID SEOS (smartmx / javacard)");
|
|
type |= HID_SEOS;
|
|
}
|
|
|
|
if (atqa == 0x0004 || atqa == 0x0048) {
|
|
printTag("EMV");
|
|
type |= MTEMV;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
printTag("Unknown tag claims to support RATS in SAK but does not...");
|
|
}
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0, SAK=0x00
|
|
|
|
if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 GetVersion
|
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
|
printTag(tag_info);
|
|
|
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion
|
|
|
|
int status = mfuc_test_authentication_support();
|
|
if (status == PM3_SUCCESS) {
|
|
// TODO: read page 2/3, then ??
|
|
printTag("MIFARE Ultralight C");
|
|
printTag("MIFARE Hospitality");
|
|
type |= MTULTRALIGHT_C;
|
|
|
|
} else {
|
|
printTag("MIFARE Ultralight");
|
|
}
|
|
|
|
}
|
|
type |= MTULTRALIGHT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else { // SAK b2=1
|
|
|
|
if (sak == 0x0A) {
|
|
|
|
if (atqa == 0x0003) {
|
|
// Uses Shanghai algo
|
|
printTag("FM11RF005SH (FUDAN Shanghai Metro)");
|
|
type |= MTFUDAN;
|
|
|
|
} else if (atqa == 0x0005) {
|
|
printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)");
|
|
type |= MTFUDAN;
|
|
}
|
|
|
|
} else if (sak == 0x53) {
|
|
printTag("FM11RF08SH (FUDAN)");
|
|
type |= MTFUDAN;
|
|
}
|
|
}
|
|
|
|
if (type == MTNONE) {
|
|
PrintAndLogEx(WARNING, " failed to fingerprint");
|
|
}
|
|
return type;
|
|
}
|
|
|
|
typedef struct {
|
|
uint8_t uid0;
|
|
uint8_t uid1;
|
|
const char *desc;
|
|
} uid_label_name_t;
|
|
|
|
static const uid_label_name_t uid_label_map[] = {
|
|
// UID0, UID1, TEXT
|
|
{0x02, 0x84, "M24SR64-Y"},
|
|
{0x02, 0xA3, "25TA02KB-P"},
|
|
{0x02, 0xC4, "25TA64K"},
|
|
{0x02, 0xE3, "25TA02KB"},
|
|
{0x02, 0xE4, "25TA512B"},
|
|
{0x02, 0xF3, "25TA02KB-D"},
|
|
{0x11, 0x22, "NTAG21x Modifiable"},
|
|
{0x00, 0x00, "None"}
|
|
};
|
|
|
|
static void getTagLabel(uint8_t uid0, uint8_t uid1) {
|
|
int i = 0;
|
|
while (uid_label_map[i].uid0 != 0x00) {
|
|
if ((uid_label_map[i].uid0 == uid0) && (uid_label_map[i].uid1 == uid1)) {
|
|
PrintAndLogEx(SUCCESS, _YELLOW_(" %s"), uid_label_map[i].desc);
|
|
return;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
static void get_compact_tlv(uint8_t *d, uint8_t n) {
|
|
d++;
|
|
n--;
|
|
|
|
while (n > 0) {
|
|
uint8_t tag = NIBBLE_HIGH(d[0]);
|
|
uint8_t len = NIBBLE_LOW(d[0]);
|
|
|
|
switch (tag) {
|
|
case 1:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Country code in (ISO 3166-1)", tag, len, sprint_hex_inrow(d + 1, len));
|
|
// iso3166 script in cmdlffdb.c is buggy, Åland, Australia not showing. getline issues
|
|
break;
|
|
case 2:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Issuer identification number (ISO 7812-1)", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
case 3:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Card service data byte", tag, len, sprint_hex_inrow(d + 1, len));
|
|
PrintAndLogEx(INFO, " %c....... Application selection: by full DF name", (d[1] & 0x80) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .%c...... Application selection: by partial DF name", (d[1] & 0x40) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ..%c..... BER-TLV data objects available in EF.DIR", (d[1] & 0x20) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ...%c.... BER-TLV data objects available in EF.ATR", (d[1] & 0x10) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ....%c... EF.DIR and EF.ATR access services: by READ BINARY command", (d[1] & 0x08) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .....%c.. EF.DIR and EF.ATR access services: by GET DATA command", (d[1] & 0x04) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ......%c. EF.DIR and EF.ATR access services: by GET RECORD(s) command", (d[1] & 0x02) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .......%c EF.DIR and EF.ATR access services: RFU", (d[1] & 0x01) ? '1' : '0');
|
|
break;
|
|
case 4:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Initial access data", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
case 5:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Card issuer data", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
case 6:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Pre-issuing data", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
case 7:
|
|
PrintAndLogEx(INFO, " %1x%1x " _YELLOW_("%s") " Card capabilities", tag, len, sprint_hex_inrow(d + 1, len));
|
|
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") " - Selection methods", d[1]);
|
|
PrintAndLogEx(INFO, " %c....... DF selection by full DF name", (d[1] & 0x80) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .%c...... DF selection by partial DF name", (d[1] & 0x40) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ..%c..... DF selection by path", (d[1] & 0x20) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ...%c.... DF selection by file identifier", (d[1] & 0x10) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ....%c... Implicit DF selection", (d[1] & 0x08) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .....%c.. Short EF identifier supported", (d[1] & 0x04) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " ......%c. Record number supported", (d[1] & 0x02) ? '1' : '0');
|
|
PrintAndLogEx(INFO, " .......%c Record identifier supported", (d[1] & 0x01) ? '1' : '0');
|
|
|
|
if (len > 1) {
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") " - Data coding byte", d[2]);
|
|
}
|
|
if (len > 2) {
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") " - Command chaining, length fields and logical channels", d[3]);
|
|
}
|
|
break;
|
|
case 8:
|
|
PrintAndLogEx(INFO, " %1x%1x ... " _YELLOW_("%s") " Status indicator", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
case 0xE:
|
|
PrintAndLogEx(INFO, " %1x%1x ... " _YELLOW_("%s") " Application identifier", tag, len, sprint_hex_inrow(d + 1, len));
|
|
break;
|
|
}
|
|
|
|
if (len > n)
|
|
break;
|
|
|
|
n -= (1 + len);
|
|
d += (1 + len);
|
|
}
|
|
}
|
|
|
|
int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
|
|
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select timeout");
|
|
DropField();
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
iso14a_card_select_t card;
|
|
memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
/*
|
|
0: couldn't read
|
|
1: OK, with ATS
|
|
2: OK, no ATS
|
|
3: proprietary Anticollision
|
|
4: OK, SAK = no ATS but RATS possible (tested below)
|
|
*/
|
|
uint64_t select_status = resp.oldarg[0];
|
|
|
|
if (select_status == 0) {
|
|
PrintAndLogEx(DEBUG, "iso14443a card select failed");
|
|
DropField();
|
|
return select_status;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
if (select_status == 3) {
|
|
PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision");
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "ATQA: %02X %02X", card.atqa[1], card.atqa[0]);
|
|
}
|
|
|
|
// identify TOPAZ
|
|
if (card.atqa[1] == 0x0C && card.atqa[0] == 0x00) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf topaz info`"));
|
|
}
|
|
|
|
DropField();
|
|
return select_status;
|
|
}
|
|
|
|
// 2: try to request ATS even if tag claims not to support it. If yes => 4
|
|
if (select_status == 2) {
|
|
uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, sizeof(rats));
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
memcpy(card.ats, resp.data.asBytes, resp.oldarg[0]);
|
|
card.ats_len = resp.oldarg[0]; // note: ats_len includes CRC Bytes
|
|
if (card.ats_len > 3) {
|
|
select_status = 4;
|
|
}
|
|
}
|
|
uint8_t ats_hist_pos = 0;
|
|
if ((card.ats_len > 3) && (card.ats[0] > 1)) {
|
|
ats_hist_pos = 2;
|
|
ats_hist_pos += (card.ats[1] & 0x10) == 0x10;
|
|
ats_hist_pos += (card.ats[1] & 0x20) == 0x20;
|
|
ats_hist_pos += (card.ats[1] & 0x40) == 0x40;
|
|
}
|
|
|
|
|
|
version_hw_t version_hw = {0};
|
|
// if 4b UID or NXP, try to get version
|
|
int res = hf14a_getversion_data(&card, select_status, &version_hw);
|
|
bool version_hw_available = (res == PM3_SUCCESS);
|
|
|
|
PrintAndLogEx(INFO, "---------- " _CYAN_("ISO14443-A Information") " ----------");
|
|
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s") " %s", sprint_hex(card.uid, card.uidlen), get_uid_type(&card));
|
|
PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]);
|
|
PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, select_status);
|
|
if (version_hw_available) {
|
|
PrintAndLogEx(DEBUG, "GetV: " _GREEN_("%s"), sprint_hex((uint8_t *)&version_hw, sizeof(version_hw)));
|
|
}
|
|
|
|
bool isMifareMini = false;
|
|
bool isMifareClassic = true;
|
|
bool isMifareDESFire = false;
|
|
bool isMifarePlus = false;
|
|
bool isMifareUltralight = false;
|
|
bool isST = false;
|
|
bool isEMV = false;
|
|
bool isFUDAN = false;
|
|
bool isISO18092 = false;
|
|
bool isNTAG424 = false;
|
|
bool isSEOS = false;
|
|
int nxptype = MTNONE;
|
|
|
|
if (card.uidlen <= 4) {
|
|
|
|
PrintAndLogEx(SUCCESS, "Possible types:");
|
|
nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]),
|
|
select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos,
|
|
version_hw_available, &version_hw
|
|
);
|
|
|
|
isMifareMini = ((nxptype & MTMINI) == MTMINI);
|
|
isMifareClassic = ((nxptype & MTCLASSIC) == MTCLASSIC);
|
|
isMifareDESFire = ((nxptype & MTDESFIRE) == MTDESFIRE);
|
|
isMifarePlus = ((nxptype & MTPLUS) == MTPLUS);
|
|
isMifareUltralight = ((nxptype & MTULTRALIGHT) == MTULTRALIGHT);
|
|
isNTAG424 = ((nxptype & MT424) == MT424);
|
|
isFUDAN = ((nxptype & MTFUDAN) == MTFUDAN);
|
|
isEMV = ((nxptype & MTEMV) == MTEMV);
|
|
isISO18092 = ((nxptype & MTISO18092) == MTISO18092);
|
|
isSEOS = ((nxptype & HID_SEOS) == HID_SEOS);
|
|
|
|
// generic catch, we assume MIFARE Classic for all unknown ISO14443a tags
|
|
isMifareClassic |= ((nxptype & MTOTHER) == MTOTHER);
|
|
|
|
} else {
|
|
|
|
// Double & triple sized UID, can be mapped to a manufacturer.
|
|
PrintAndLogEx(SUCCESS, " " _YELLOW_("%s"), getTagInfo(card.uid[0]));
|
|
PrintAndLogEx(SUCCESS, "Possible types:");
|
|
|
|
switch (card.uid[0]) {
|
|
case 0x02: { // ST
|
|
isST = true;
|
|
isMifareClassic = false;
|
|
|
|
uint16_t atqa = MemLeToUint2byte(card.atqa);
|
|
if (atqa == 0x0044) {
|
|
printTag("EMV");
|
|
isEMV = true;
|
|
}
|
|
break;
|
|
}
|
|
case 0x04: { // NXP
|
|
nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]),
|
|
select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos,
|
|
version_hw_available, &version_hw
|
|
);
|
|
|
|
isMifareMini = ((nxptype & MTMINI) == MTMINI);
|
|
isMifareClassic = ((nxptype & MTCLASSIC) == MTCLASSIC);
|
|
isMifareDESFire = ((nxptype & MTDESFIRE) == MTDESFIRE);
|
|
isMifarePlus = ((nxptype & MTPLUS) == MTPLUS);
|
|
isMifareUltralight = ((nxptype & MTULTRALIGHT) == MTULTRALIGHT);
|
|
isNTAG424 = ((nxptype & MT424) == MT424);
|
|
|
|
if ((nxptype & MTOTHER) == MTOTHER) {
|
|
isMifareClassic = true;
|
|
}
|
|
|
|
if ((nxptype & MTFUDAN) == MTFUDAN) {
|
|
isFUDAN = true;
|
|
}
|
|
|
|
if ((nxptype & MTEMV) == MTEMV) {
|
|
isEMV = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 0x05: { // Infineon
|
|
if ((card.uid[1] & 0xF0) == 0x10) {
|
|
printTag("my-d(tm) command set SLE 66R04/16/32P, SLE 66R04/16/32S");
|
|
} else if ((card.uid[1] & 0xF0) == 0x20) {
|
|
printTag("my-d(tm) command set SLE 66R01/16/32P (Type 2 Tag)");
|
|
} else if ((card.uid[1] & 0xF0) == 0x30) {
|
|
printTag("my-d(tm) move lean SLE 66R01P/66R01PN");
|
|
} else if ((card.uid[1] & 0xF0) == 0x70) {
|
|
printTag("my-d(tm) move lean SLE 66R01L");
|
|
}
|
|
isMifareUltralight = true;
|
|
isMifareClassic = false;
|
|
|
|
if (card.sak == 0x88) {
|
|
printTag("Infineon MIFARE CLASSIC 1K");
|
|
isMifareUltralight = false;
|
|
isMifareClassic = true;
|
|
}
|
|
getTagLabel(card.uid[0], card.uid[1]);
|
|
break;
|
|
}
|
|
case 0x46: {
|
|
if (memcmp(card.uid, "FSTN10m", 7) == 0) {
|
|
isMifareClassic = false;
|
|
printTag("Waveshare NFC-Powered e-Paper 1.54\" (please disregard MANUFACTURER mapping above)");
|
|
}
|
|
break;
|
|
}
|
|
case 0x57: {
|
|
if (memcmp(card.uid, "WSDZ10m", 7) == 0) {
|
|
isMifareClassic = false;
|
|
printTag("Waveshare NFC-Powered e-Paper (please disregard MANUFACTURER mapping above)");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
getTagLabel(card.uid[0], card.uid[1]);
|
|
switch (card.sak) {
|
|
case 0x00: {
|
|
isMifareClassic = false;
|
|
|
|
// ******** is card of the MFU type (UL/ULC/NTAG/ etc etc)
|
|
DropField();
|
|
|
|
uint64_t tagT = GetHF14AMfU_Type();
|
|
if (tagT != MFU_TT_UL_ERROR) {
|
|
ul_print_type(tagT, 0);
|
|
isMifareUltralight = true;
|
|
printTag("MIFARE Ultralight/C/NTAG Compatible");
|
|
} else {
|
|
printTag("Possible AZTEK (iso14443a compliant)");
|
|
}
|
|
|
|
// reconnect for further tests
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
DropField();
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
|
|
|
|
select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS
|
|
|
|
if (select_status == 0) {
|
|
DropField();
|
|
return select_status;
|
|
}
|
|
break;
|
|
}
|
|
case 0x0A: {
|
|
if (card.atqa[0] == 0x03) {
|
|
// Uses Shanghai algo
|
|
printTag("FM11RF005SH (FUDAN Shanghai Metro)");
|
|
|
|
} else if (card.atqa[0] == 0x05) {
|
|
// Uses MIFARE Crypto-1 algo
|
|
printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)");
|
|
}
|
|
break;
|
|
}
|
|
case 0x20: {
|
|
printTag("JCOP 31/41");
|
|
break;
|
|
}
|
|
case 0x28: {
|
|
printTag("JCOP31 or JCOP41 v2.3.1");
|
|
break;
|
|
}
|
|
case 0x38: {
|
|
printTag("Nokia 6212 or 6131");
|
|
break;
|
|
}
|
|
case 0x53: {
|
|
printTag("FM11RF08SH (FUDAN)");
|
|
break;
|
|
}
|
|
case 0x98: {
|
|
printTag("Gemplus MPCOS");
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (card.ats_len >= 3) { // a valid ATS consists of at least the length byte (TL) and 2 CRC bytes
|
|
|
|
PrintAndLogEx(INFO, "-------------------------- " _CYAN_("ATS") " ----------------------------------");
|
|
bool ta1 = 0, tb1 = 0, tc1 = 0;
|
|
|
|
if (select_status == 2) {
|
|
PrintAndLogEx(INFO, "--> SAK incorrectly claims that card doesn't support RATS <--");
|
|
}
|
|
|
|
bool bad_ats = false;
|
|
if (card.ats[0] != card.ats_len - 2) {
|
|
PrintAndLogEx(WARNING, _RED_("ATS may be corrupted."));
|
|
PrintAndLogEx(INFO, "Length of ATS (%d bytes incl. 2 Bytes CRC) doesn't match TL", card.ats_len);
|
|
bad_ats = true;
|
|
}
|
|
|
|
if (
|
|
(card.ats_len == 7 && memcmp(card.ats, "\x05\x78\x77\x80\x02\x9C\x3A", 7) == 0) ||
|
|
(card.ats_len == 7 && memcmp(card.ats, "\x05\x78\x77\x94\x02\x6D\xC8", 7) == 0)
|
|
) {
|
|
isSEOS = true;
|
|
isNTAG424 = false;
|
|
isMifareDESFire = false;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "ATS: " _YELLOW_("%s")"[ %02X %02X ]", sprint_hex(card.ats, card.ats_len - 2), card.ats[card.ats_len - 2], card.ats[card.ats_len - 1]);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") "............... TL length is " _GREEN_("%d") " bytes", card.ats[0], card.ats[0]);
|
|
|
|
if (bad_ats == false) {
|
|
|
|
if ((card.ats[0] > 1) && (card.ats_len > 3)) { // there is a format byte (T0)
|
|
ta1 = (card.ats[1] & 0x10) == 0x10;
|
|
tb1 = (card.ats[1] & 0x20) == 0x20;
|
|
tc1 = (card.ats[1] & 0x40) == 0x40;
|
|
int16_t fsci = card.ats[1] & 0x0f;
|
|
|
|
PrintAndLogEx(INFO, " ..." _YELLOW_("%02X") "............ T0 TA1 is%s present, TB1 is%s present, "
|
|
"TC1 is%s present, FSCI is %d (FSC = %d)",
|
|
card.ats[1],
|
|
(ta1 ? "" : _RED_(" NOT")),
|
|
(tb1 ? "" : _RED_(" NOT")),
|
|
(tc1 ? "" : _RED_(" NOT")),
|
|
fsci,
|
|
fsci < ARRAYLEN(atsFSC) ? atsFSC[fsci] : -1
|
|
);
|
|
}
|
|
int pos = 2;
|
|
if (ta1 && (card.ats_len > pos + 2)) {
|
|
char dr[16], ds[16];
|
|
dr[0] = ds[0] = '\0';
|
|
if (card.ats[pos] & 0x10) strcat(ds, "2, ");
|
|
if (card.ats[pos] & 0x20) strcat(ds, "4, ");
|
|
if (card.ats[pos] & 0x40) strcat(ds, "8, ");
|
|
if (card.ats[pos] & 0x01) strcat(dr, "2, ");
|
|
if (card.ats[pos] & 0x02) strcat(dr, "4, ");
|
|
if (card.ats[pos] & 0x04) strcat(dr, "8, ");
|
|
if (strlen(ds) != 0) ds[strlen(ds) - 2] = '\0';
|
|
if (strlen(dr) != 0) dr[strlen(dr) - 2] = '\0';
|
|
PrintAndLogEx(INFO, " ......" _YELLOW_("%02X") "......... TA1 different divisors are%s supported, "
|
|
"DR: [%s], DS: [%s]",
|
|
card.ats[pos],
|
|
((card.ats[pos] & 0x80) ? _RED_(" NOT") : ""),
|
|
dr,
|
|
ds
|
|
);
|
|
|
|
pos++;
|
|
}
|
|
|
|
if (tb1 && (card.ats_len > pos + 2)) {
|
|
uint32_t sfgi = card.ats[pos] & 0x0F;
|
|
uint32_t fwi = card.ats[pos] >> 4;
|
|
|
|
PrintAndLogEx(INFO, " ........." _YELLOW_("%02X") "...... TB1 SFGI = %d (SFGT = %s%d/fc), FWI = " _YELLOW_("%d") " (FWT = %d/fc)",
|
|
card.ats[pos],
|
|
(sfgi),
|
|
sfgi ? "" : "(not needed) ",
|
|
sfgi ? (1 << 12) << sfgi : 0,
|
|
fwi,
|
|
(1 << 12) << fwi
|
|
);
|
|
pos++;
|
|
}
|
|
|
|
if (tc1 && (card.ats_len > pos + 2)) {
|
|
PrintAndLogEx(INFO, " ............" _YELLOW_("%02X") "... TC1 NAD is%s supported, CID is%s supported",
|
|
card.ats[pos],
|
|
(card.ats[pos] & 0x01) ? "" : _RED_(" NOT"),
|
|
(card.ats[pos] & 0x02) ? "" : _RED_(" NOT")
|
|
);
|
|
pos++;
|
|
}
|
|
|
|
// ATS - Historial bytes and identify based on it
|
|
if ((card.ats[0] > pos) && (card.ats_len >= card.ats[0] + 2)) {
|
|
uint8_t calen = card.ats[0] - pos;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "-------------------- " _CYAN_("Historical bytes") " ---------------------------");
|
|
|
|
if (card.ats[pos] == 0xC1) {
|
|
PrintAndLogEx(INFO, " %s", sprint_hex(card.ats + pos, calen));
|
|
PrintAndLogEx(SUCCESS, " C1..................... Mifare or (multiple) virtual cards of various type");
|
|
PrintAndLogEx(SUCCESS, " %02X.................. length is " _YELLOW_("%d") " bytes", card.ats[pos + 1], card.ats[pos + 1]);
|
|
switch (card.ats[pos + 2] & 0xf0) {
|
|
case 0x10:
|
|
PrintAndLogEx(SUCCESS, " 1x............... MIFARE DESFire");
|
|
isMifareDESFire = true;
|
|
isMifareClassic = false;
|
|
isMifarePlus = false;
|
|
break;
|
|
case 0x20:
|
|
PrintAndLogEx(SUCCESS, " 2x............... MIFARE Plus");
|
|
isMifarePlus = true;
|
|
isMifareDESFire = false;
|
|
isMifareClassic = false;
|
|
break;
|
|
}
|
|
switch (card.ats[pos + 2] & 0x0f) {
|
|
case 0x00:
|
|
PrintAndLogEx(SUCCESS, " x0............... < 1 kByte");
|
|
break;
|
|
case 0x01:
|
|
PrintAndLogEx(SUCCESS, " x1............... 1 kByte");
|
|
break;
|
|
case 0x02:
|
|
PrintAndLogEx(SUCCESS, " x2............... 2 kByte");
|
|
break;
|
|
case 0x03:
|
|
PrintAndLogEx(SUCCESS, " x3............... 4 kByte");
|
|
break;
|
|
case 0x04:
|
|
PrintAndLogEx(SUCCESS, " x4............... 8 kByte");
|
|
break;
|
|
}
|
|
switch (card.ats[pos + 3] & 0xf0) {
|
|
case 0x00:
|
|
PrintAndLogEx(SUCCESS, " 0x............ Engineering sample");
|
|
break;
|
|
case 0x20:
|
|
PrintAndLogEx(SUCCESS, " 2x............ Released");
|
|
break;
|
|
}
|
|
switch (card.ats[pos + 3] & 0x0f) {
|
|
case 0x00:
|
|
PrintAndLogEx(SUCCESS, " x0............ Generation 1");
|
|
break;
|
|
case 0x01:
|
|
PrintAndLogEx(SUCCESS, " x1............ Generation 2");
|
|
break;
|
|
case 0x02:
|
|
PrintAndLogEx(SUCCESS, " x2............ Generation 3");
|
|
break;
|
|
}
|
|
switch (card.ats[pos + 4] & 0x0f) {
|
|
case 0x00:
|
|
PrintAndLogEx(SUCCESS, " x0......... Only VCSL supported");
|
|
break;
|
|
case 0x01:
|
|
PrintAndLogEx(SUCCESS, " x1......... VCS, VCSL, and SVC supported");
|
|
break;
|
|
case 0x0E:
|
|
PrintAndLogEx(SUCCESS, " xE......... no VCS command supported");
|
|
break;
|
|
}
|
|
} else {
|
|
|
|
if (card.ats[pos] == 0x80 || card.ats[pos] == 0x00) {
|
|
PrintAndLogEx(SUCCESS, "%s (compact TLV data object)", sprint_hex_inrow(&card.ats[pos], calen));
|
|
get_compact_tlv(card.ats + pos, calen);
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "%s - %s"
|
|
, sprint_hex_inrow(card.ats + pos, calen)
|
|
, sprint_ascii(card.ats + pos, calen)
|
|
);
|
|
}
|
|
if (calen > 1) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "------------------ " _CYAN_("ATR fingerprinting") " ---------------------------");
|
|
uint8_t atr[256] = {0};
|
|
int atrLen = 0;
|
|
atsToEmulatedAtr(card.ats, atr, &atrLen);
|
|
char *copy = str_dup(getAtrInfo(sprint_hex_inrow(atr, atrLen)));
|
|
char *token = strtok(copy, "\n");
|
|
while (token != NULL) {
|
|
PrintAndLogEx(SUCCESS, " %s", token);
|
|
token = strtok(NULL, "\n");
|
|
}
|
|
free(copy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (do_aid_search) {
|
|
|
|
PrintAndLogEx(INFO, "-------------------- " _CYAN_("AID Search") " --------------------");
|
|
|
|
json_t *root = AIDSearchInit(verbose);
|
|
if (root != NULL) {
|
|
bool found = false;
|
|
bool ActivateField = true;
|
|
for (size_t elmindx = 0; elmindx < json_array_size(root); elmindx++) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
break;
|
|
}
|
|
|
|
json_t *data = AIDSearchGetElm(root, elmindx);
|
|
uint8_t vaid[200] = {0};
|
|
int vaidlen = 0;
|
|
if ((AIDGetFromElm(data, vaid, sizeof(vaid), &vaidlen) == false) || (vaidlen == 0)) {
|
|
continue;
|
|
}
|
|
if (AIDSeenBefore(root, vaid, (size_t)vaidlen, elmindx)) {
|
|
continue;
|
|
}
|
|
|
|
uint16_t sw = 0;
|
|
uint8_t result[1024] = {0};
|
|
size_t resultlen = 0;
|
|
res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, vaid, vaidlen, result, sizeof(result), &resultlen, &sw);
|
|
ActivateField = false;
|
|
if (res) {
|
|
continue;
|
|
}
|
|
|
|
uint8_t dfname[200] = {0};
|
|
size_t dfnamelen = 0;
|
|
if (resultlen > 3) {
|
|
struct tlvdb *tlv = tlvdb_parse_multi(result, resultlen);
|
|
if (tlv) {
|
|
// 0x84 Dedicated File (DF) Name
|
|
const struct tlv *dfnametlv = tlvdb_get_tlv(tlvdb_find_full(tlv, 0x84));
|
|
if (dfnametlv) {
|
|
dfnamelen = dfnametlv->len;
|
|
memcpy(dfname, dfnametlv->value, dfnamelen);
|
|
}
|
|
tlvdb_free(tlv);
|
|
}
|
|
}
|
|
|
|
if (sw == ISO7816_OK || sw == ISO7816_INVALID_DF || sw == ISO7816_FILE_TERMINATED) {
|
|
if (sw == ISO7816_OK) {
|
|
if (verbose) PrintAndLogEx(SUCCESS, "Application ( " _GREEN_("ok") " )");
|
|
} else {
|
|
if (verbose) PrintAndLogEx(WARNING, "Application ( " _RED_("blocked") " )");
|
|
}
|
|
|
|
uint8_t response_for_match[1026] = {0};
|
|
size_t response_for_match_len = resultlen;
|
|
memcpy(response_for_match, result, MIN(resultlen, sizeof(response_for_match)));
|
|
if ((response_for_match_len + 2) <= sizeof(response_for_match)) {
|
|
response_for_match[response_for_match_len] = sw >> 8;
|
|
response_for_match[response_for_match_len + 1] = sw & 0xff;
|
|
response_for_match_len += 2;
|
|
}
|
|
PrintAIDDescriptionEx(root, sprint_hex_inrow(vaid, vaidlen), response_for_match, response_for_match_len, verbose);
|
|
|
|
if (dfnamelen) {
|
|
if (dfnamelen == vaidlen) {
|
|
|
|
if (memcmp(dfname, vaid, vaidlen) == 0) {
|
|
if (verbose) PrintAndLogEx(INFO, "(DF) Name found and equal to AID");
|
|
} else {
|
|
PrintAndLogEx(INFO, "(DF) Name not equal to AID: " _YELLOW_("%s"), sprint_hex_inrow(dfname, dfnamelen));
|
|
PrintAIDDescriptionBuf(root, dfname, dfnamelen, verbose);
|
|
}
|
|
|
|
} else {
|
|
PrintAndLogEx(INFO, "(DF) Name not equal to AID: " _YELLOW_("%s"), sprint_hex_inrow(dfname, dfnamelen));
|
|
PrintAIDDescriptionBuf(root, dfname, dfnamelen, verbose);
|
|
}
|
|
|
|
} else {
|
|
if (verbose) PrintAndLogEx(INFO, "(DF) Name not found");
|
|
}
|
|
|
|
if (verbose) PrintAndLogEx(SUCCESS, "----------------------------------------------------");
|
|
found = true;
|
|
isEMV = true;
|
|
}
|
|
|
|
}
|
|
DropField();
|
|
if (verbose == false && found) {
|
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isISO18092) {
|
|
PrintAndLogEx(INFO, "proprietary iso18092 card found");
|
|
} else {
|
|
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "Proprietary non iso14443-4 card found");
|
|
PrintAndLogEx(INFO, "RATS not supported");
|
|
if ((card.sak & 0x20) == 0x20) {
|
|
PrintAndLogEx(INFO, "--> SAK incorrectly claims that card supports RATS <--");
|
|
}
|
|
}
|
|
|
|
if (select_status == 1) {
|
|
select_status = 2;
|
|
}
|
|
}
|
|
|
|
if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
// PrintAndLogEx(INFO, "");
|
|
|
|
uint16_t isMagic = 0;
|
|
|
|
if (isMifareClassic || isMifareMini) {
|
|
isMagic = detect_mf_magic(true, MF_KEY_B, 0xFFFFFFFFFFFF);
|
|
}
|
|
|
|
if (isMifareUltralight) {
|
|
isMagic = detect_mf_magic(false, MF_KEY_A, 0);
|
|
}
|
|
|
|
if (isMifareClassic || isMifareMini) {
|
|
res = detect_classic_static_nonce();
|
|
if (res == NONCE_STATIC) {
|
|
PrintAndLogEx(SUCCESS, "Static nonce....... " _YELLOW_("yes"));
|
|
}
|
|
|
|
if (res == NONCE_NORMAL) {
|
|
// not static
|
|
res = detect_classic_prng();
|
|
if (res == 1) {
|
|
PrintAndLogEx(SUCCESS, "Prng detection..... " _GREEN_("weak"));
|
|
} else if (res == 0) {
|
|
PrintAndLogEx(SUCCESS, "Prng detection..... " _YELLOW_("hard"));
|
|
} else {
|
|
PrintAndLogEx(FAILED, "Prng detection...... " _RED_("fail"));
|
|
}
|
|
|
|
if (do_nack_test) {
|
|
detect_classic_nackbug(false);
|
|
}
|
|
}
|
|
|
|
uint8_t signature[32] = {0};
|
|
res = read_mfc_ev1_signature(signature);
|
|
if (res == PM3_SUCCESS) {
|
|
mfc_ev1_print_signature(card.uid, card.uidlen, signature, sizeof(signature));
|
|
}
|
|
}
|
|
|
|
if (setDeviceDebugLevel(dbg_curr, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (isMifareUltralight) {
|
|
|
|
if (((isMagic & MAGIC_FLAG_GEN_1A) == MAGIC_FLAG_GEN_1A) || ((isMagic & MAGIC_FLAG_GEN_1B) == MAGIC_FLAG_GEN_1B)) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mfu *") "` magic commands");
|
|
}
|
|
|
|
if ((isMagic & MAGIC_FLAG_NTAG21X) == MAGIC_FLAG_NTAG21X) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mfu *") "` magic commands");
|
|
}
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu info") "`");
|
|
}
|
|
|
|
if (isMifarePlus && (isMagic == MAGIC_FLAG_NONE)) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfp info") "`");
|
|
}
|
|
|
|
if (isMifareDESFire && (isMagic == MAGIC_FLAG_NONE) && isEMV == false) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfdes info") "`");
|
|
}
|
|
|
|
if (isST) {
|
|
if (card.ats_len > 0) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf st25ta info") "`");
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu info") "`");
|
|
}
|
|
}
|
|
|
|
if (isEMV) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("emv reader") "`");
|
|
}
|
|
|
|
if (isSEOS) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf seos info") "`");
|
|
}
|
|
|
|
if (isFUDAN) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf fudan dump") "`");
|
|
/*
|
|
PrintAndLogEx(HINT, " hf 14a raw -a -b 7 -k 26");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3000");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3001");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3002");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3003");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3004");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3005");
|
|
PrintAndLogEx(HINT, " hf 14a raw -k -c 3006");
|
|
PrintAndLogEx(HINT, " hf 14a raw -c 3007");
|
|
*/
|
|
}
|
|
|
|
if (isNTAG424) {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf ntag424 info") "`");
|
|
}
|
|
|
|
if (isMifareClassic || isMifareMini) {
|
|
if (((isMagic & MAGIC_FLAG_GEN_1A) == MAGIC_FLAG_GEN_1A) || ((isMagic & MAGIC_FLAG_GEN_1B) == MAGIC_FLAG_GEN_1B)) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf c*") "` magic commands");
|
|
|
|
// if GEN4 GDM in Gen1a more, hint about it
|
|
if (((isMagic & MAGIC_FLAG_GDM_WUP_40) == MAGIC_FLAG_GDM_WUP_40) || ((isMagic & MAGIC_FLAG_GDM_WUP_40_ZUID) == MAGIC_FLAG_GDM_WUP_40_ZUID)) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf gdm* --gen1a") "` magic commands");
|
|
}
|
|
}
|
|
|
|
if ((isMagic & MAGIC_FLAG_GEN_3) == MAGIC_FLAG_GEN_3) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf gen3*") "` magic commands");
|
|
}
|
|
|
|
if ((isMagic & MAGIC_FLAG_GEN_4GTU) == MAGIC_FLAG_GEN_4GTU) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf g*") "` magic commands");
|
|
}
|
|
|
|
if ((isMagic & MAGIC_FLAG_GDM_AUTH) == MAGIC_FLAG_GDM_AUTH) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf gdm*") "` magic commands");
|
|
}
|
|
|
|
if ((isMagic & MAGIC_FLAG_GEN_2) == MAGIC_FLAG_GEN_2) {
|
|
PrintAndLogEx(HINT, "Hint: Use `" _YELLOW_("hf mf") "` commands");
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mf info`"));
|
|
}
|
|
}
|
|
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
DropField();
|
|
return select_status;
|
|
}
|
|
|
|
static bool hint_aid_match_select_response(const hintAIDList_t *entry, const uint8_t *select_response, size_t select_response_len) {
|
|
if (entry->select_response_match_length == 0) {
|
|
return true;
|
|
}
|
|
|
|
if ((entry->select_response_match == NULL) || (select_response == NULL) || (select_response_len < entry->select_response_match_length)) {
|
|
return false;
|
|
}
|
|
|
|
return byte_strstr(select_response, select_response_len, (const uint8_t *)entry->select_response_match, entry->select_response_match_length) != -1;
|
|
}
|
|
|
|
int infoHF14A4Applications(bool verbose) {
|
|
bool cardFound[ARRAYLEN(hintAIDList)] = {0};
|
|
bool ActivateField = true;
|
|
int found = 0;
|
|
for (int i = 0; i < ARRAYLEN(hintAIDList); i++) {
|
|
uint16_t sw = 0;
|
|
uint8_t result[1024] = {0};
|
|
size_t resultlen = 0;
|
|
int res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, (uint8_t *)hintAIDList[i].aid, hintAIDList[i].aid_length, result, sizeof(result), &resultlen, &sw);
|
|
ActivateField = false;
|
|
if (res)
|
|
break;
|
|
|
|
if (sw == ISO7816_OK || sw == ISO7816_INVALID_DF || sw == ISO7816_FILE_TERMINATED) {
|
|
if (!hint_aid_match_select_response(&hintAIDList[i], result, resultlen)) {
|
|
continue;
|
|
}
|
|
|
|
if (!found) {
|
|
if (verbose)
|
|
PrintAndLogEx(INFO, "----------------- " _CYAN_("Short AID search") " -----------------");
|
|
}
|
|
found++;
|
|
|
|
if (sw == ISO7816_OK) {
|
|
if (verbose)
|
|
PrintAndLogEx(SUCCESS, "Application " _CYAN_("%s") " ( " _GREEN_("ok") " )", hintAIDList[i].desc);
|
|
cardFound[i] = true;
|
|
} else if (verbose) {
|
|
PrintAndLogEx(WARNING, "Application " _CYAN_("%s") " ( " _RED_("blocked") " )", hintAIDList[i].desc);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "---------------------------------------------------");
|
|
}
|
|
|
|
if (found >= ARRAYLEN(hintAIDList) - 1) {
|
|
PrintAndLogEx(HINT, "Hint: Card answers to all AID. It maybe the latest revision of plus/desfire/ultralight card.");
|
|
} else {
|
|
for (int i = 0; i < ARRAYLEN(hintAIDList); i++) {
|
|
if (cardFound[i] && strlen(hintAIDList[i].hint))
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("%s") "` commands", hintAIDList[i].hint);
|
|
}
|
|
}
|
|
}
|
|
|
|
DropField();
|
|
return found;
|
|
}
|
|
|
|
static uint32_t inc_sw_error_occurrence(uint16_t sw, uint32_t *all_sw) {
|
|
uint8_t sw1 = (uint8_t)(sw >> 8);
|
|
uint8_t sw2 = (uint8_t)(0xff & sw);
|
|
|
|
// Don't count successes
|
|
if (sw1 == 0x90 && sw2 == 0x00) {
|
|
return 0;
|
|
}
|
|
|
|
// Always max "Instruction not supported"
|
|
if (sw1 == 0x6D && sw2 == 0x00) {
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
all_sw[(sw1 * 256) + sw2]++;
|
|
|
|
return all_sw[(sw1 * 256) + sw2];
|
|
}
|
|
|
|
static int CmdHf14AFindapdu(const char *Cmd) {
|
|
// TODO: Option to select AID/File (and skip INS 0xA4).
|
|
// TODO: Check all instructions with extended APDUs if the card support it.
|
|
// TODO: Option to reset tag before every command.
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a apdufind",
|
|
"Enumerate APDU's of ISO7816 protocol to find valid CLS/INS/P1/P2 commands.\n"
|
|
"It loops all 256 possible values for each byte.\n"
|
|
"The loop oder is INS -> P1/P2 (alternating) -> CLA.\n"
|
|
"Tag must be on antenna before running.",
|
|
"hf 14a apdufind\n"
|
|
"hf 14a apdufind --cla 80\n"
|
|
"hf 14a apdufind --cla 80 --error-limit 20 --skip-ins a4 --skip-ins b0 --with-le\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("c", "cla", "<hex>", "Start value of CLASS (1 hex byte)"),
|
|
arg_str0("i", "ins", "<hex>", "Start value of INSTRUCTION (1 hex byte)"),
|
|
arg_str0(NULL, "p1", "<hex>", "Start value of P1 (1 hex byte)"),
|
|
arg_str0(NULL, "p2", "<hex>", "Start value of P2 (1 hex byte)"),
|
|
arg_u64_0("r", "reset", "<number>", "Minimum secondes before resetting the tag (to prevent timeout issues). Default is 5 minutes"),
|
|
arg_u64_0("e", "error-limit", "<number>", "Maximum times an status word other than 0x9000 or 0x6D00 is shown. Default is 512."),
|
|
arg_strx0("s", "skip-ins", "<hex>", "Do not test an instruction (can be specified multiple times)"),
|
|
arg_lit0("l", "with-le", "Search for APDUs with Le=0 (case 2S) as well"),
|
|
arg_lit0("v", "verbose", "Verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int cla_len = 0;
|
|
uint8_t cla_arg[1] = {0};
|
|
CLIGetHexWithReturn(ctx, 1, cla_arg, &cla_len);
|
|
|
|
int ins_len = 0;
|
|
uint8_t ins_arg[1] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, ins_arg, &ins_len);
|
|
|
|
int p1_len = 0;
|
|
uint8_t p1_arg[1] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, p1_arg, &p1_len);
|
|
|
|
int p2_len = 0;
|
|
uint8_t p2_arg[1] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, p2_arg, &p2_len);
|
|
|
|
uint64_t reset_time = arg_get_u64_def(ctx, 5, 5 * 60);
|
|
uint32_t error_limit = arg_get_u64_def(ctx, 6, 512);
|
|
|
|
int ignore_ins_len = 0;
|
|
uint8_t ignore_ins_arg[250] = {0};
|
|
CLIGetHexWithReturn(ctx, 7, ignore_ins_arg, &ignore_ins_len);
|
|
|
|
bool with_le = arg_get_lit(ctx, 8);
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
bool activate_field = true;
|
|
bool keep_field_on = true;
|
|
uint8_t cla = cla_arg[0];
|
|
uint8_t ins = ins_arg[0];
|
|
uint8_t p1 = p1_arg[0];
|
|
uint8_t p2 = p2_arg[0];
|
|
|
|
uint8_t response[PM3_CMD_DATA_SIZE] = {0};
|
|
int response_n = 0;
|
|
uint8_t aSELECT_AID[80];
|
|
int aSELECT_AID_n = 0;
|
|
|
|
// Check if the tag reponds to APDUs.
|
|
PrintAndLogEx(INFO, "Sending a test APDU (select file command) to check if the tag is responding to APDU");
|
|
param_gethex_to_eol("00a404000aa000000440000101000100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n);
|
|
int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, true, false, response, sizeof(response), &response_n);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Tag did not respond to a test APDU (select file command). Aborting...");
|
|
return res;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "Starting the APDU finder [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2);
|
|
|
|
bool inc_p1 = false;
|
|
bool skip_ins = false;
|
|
uint32_t all_sw[256][256] = { { 0 } };
|
|
uint32_t sw_occurrences = 0;
|
|
|
|
uint64_t t_start = msclock();
|
|
uint64_t t_last_reset = msclock();
|
|
|
|
// Enumerate APDUs.
|
|
do {
|
|
do {
|
|
do {
|
|
retry_ins:
|
|
// Exit (was the Enter key pressed)?
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(INFO, "User interrupted detected. Aborting");
|
|
goto out;
|
|
}
|
|
|
|
// Skip/Ignore this instrctuion?
|
|
for (int i = 0; i < ignore_ins_len; i++) {
|
|
if (ins == ignore_ins_arg[i]) {
|
|
skip_ins = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (skip_ins) {
|
|
skip_ins = false;
|
|
continue;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2);
|
|
}
|
|
|
|
// Send APDU without Le (case 1) and with Le = 0 (case 2S), if "with-le" was set.
|
|
uint8_t command[5] = {cla, ins, p1, p2, 0x00};
|
|
int command_n = 4;
|
|
for (int i = 0; i < 1 + with_le; i++) {
|
|
// Send APDU.
|
|
res = ExchangeAPDU14a(command, command_n + i, activate_field, keep_field_on, response, sizeof(response), &response_n);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
activate_field = true;
|
|
goto retry_ins;
|
|
}
|
|
|
|
uint16_t sw = get_sw(response, response_n);
|
|
sw_occurrences = inc_sw_error_occurrence(sw, all_sw[0]);
|
|
|
|
// Show response.
|
|
if (sw_occurrences < error_limit) {
|
|
logLevel_t log_level = INFO;
|
|
if (sw == ISO7816_OK) {
|
|
log_level = SUCCESS;
|
|
}
|
|
|
|
if (verbose == true || sw != 0x6e00) {
|
|
PrintAndLogEx(log_level, "Got response for APDU \"%s\": %04X (%s)",
|
|
sprint_hex_inrow(command, command_n + i),
|
|
sw,
|
|
GetAPDUCodeDescription(sw >> 8, sw & 0xff)
|
|
);
|
|
|
|
if (response_n > 2) {
|
|
PrintAndLogEx(SUCCESS, "Response data is: %s | %s",
|
|
sprint_hex_inrow(response, response_n - 2),
|
|
sprint_ascii(response, response_n - 2)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Do not reativate the filed until the next reset.
|
|
activate_field = false;
|
|
} while (++ins != ins_arg[0]);
|
|
|
|
// Increment P1/P2 in an alternating fashion.
|
|
if (inc_p1) {
|
|
p1++;
|
|
} else {
|
|
p2++;
|
|
}
|
|
|
|
inc_p1 = !inc_p1;
|
|
|
|
// Check if re-selecting the card is needed.
|
|
uint64_t t_since_last_reset = ((msclock() - t_last_reset) / 1000);
|
|
if (t_since_last_reset > reset_time) {
|
|
DropField();
|
|
activate_field = true;
|
|
t_last_reset = msclock();
|
|
PrintAndLogEx(INFO, "Last reset was %" PRIu64 " seconds ago. Resetting the tag to prevent timeout issues", t_since_last_reset);
|
|
}
|
|
PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2);
|
|
|
|
} while (p1 != p1_arg[0] || p2 != p2_arg[0]);
|
|
|
|
cla++;
|
|
PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2);
|
|
|
|
} while (cla != cla_arg[0]);
|
|
|
|
out:
|
|
PrintAndLogEx(SUCCESS, "Runtime: %" PRIu64 " seconds\n", (msclock() - t_start) / 1000);
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHF14ANdefRead(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a ndefread",
|
|
"Read NFC Data Exchange Format (NDEF) file on Type 4 NDEF tag",
|
|
"hf 14a ndefread\n"
|
|
"hf 14a ndefread -f myfilename -> save raw NDEF to file"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "save raw NDEF to file"),
|
|
arg_litn("v", "verbose", 0, 2, "verbose output"),
|
|
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);
|
|
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool verbose2 = arg_get_lit(ctx, 2) > 1;
|
|
CLIParserFree(ctx);
|
|
|
|
bool activate_field = true;
|
|
bool keep_field_on = true;
|
|
uint8_t response[PM3_CMD_DATA_SIZE];
|
|
int resplen = 0;
|
|
bool backward_compatibility_v1 = false;
|
|
|
|
// --------------- Select NDEF Tag application ----------------
|
|
uint8_t aSELECT_AID[80];
|
|
int aSELECT_AID_n = 0;
|
|
param_gethex_to_eol("00a4040007d276000085010100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n);
|
|
int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
if (resplen < 2) {
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint16_t sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
// Try NDEF Type 4 Tag v1.0
|
|
param_gethex_to_eol("00a4040007d2760000850100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n);
|
|
res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
if (resplen < 2) {
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "Selecting NDEF aid failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
backward_compatibility_v1 = true;
|
|
}
|
|
|
|
activate_field = false;
|
|
keep_field_on = true;
|
|
|
|
// --------------- CC file reading ----------------
|
|
uint8_t aSELECT_FILE_CC[30];
|
|
int aSELECT_FILE_CC_n = 0;
|
|
if (backward_compatibility_v1) {
|
|
param_gethex_to_eol("00a4000002e103", 0, aSELECT_FILE_CC, sizeof(aSELECT_FILE_CC), &aSELECT_FILE_CC_n);
|
|
} else {
|
|
param_gethex_to_eol("00a4000c02e103", 0, aSELECT_FILE_CC, sizeof(aSELECT_FILE_CC), &aSELECT_FILE_CC_n);
|
|
}
|
|
res = ExchangeAPDU14a(aSELECT_FILE_CC, aSELECT_FILE_CC_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "Selecting CC file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t aREAD_CC[30];
|
|
int aREAD_CC_n = 0;
|
|
param_gethex_to_eol("00b000000f", 0, aREAD_CC, sizeof(aREAD_CC), &aREAD_CC_n);
|
|
res = ExchangeAPDU14a(aREAD_CC, aREAD_CC_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "reading CC file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// Parse CC data
|
|
uint8_t cc_data[resplen - 2];
|
|
memcpy(cc_data, response, sizeof(cc_data));
|
|
uint8_t file_id[2] = {cc_data[9], cc_data[10]};
|
|
|
|
if (verbose) {
|
|
print_type4_cc_info(cc_data, sizeof(cc_data));
|
|
}
|
|
|
|
uint16_t max_rapdu_size = (cc_data[3] << 8 | cc_data[4]) - 2;
|
|
max_rapdu_size = max_rapdu_size < sizeof(response) - 2 ? max_rapdu_size : sizeof(response) - 2;
|
|
|
|
// --------------- NDEF file reading ----------------
|
|
uint8_t aSELECT_FILE_NDEF[30];
|
|
int aSELECT_FILE_NDEF_n = 0;
|
|
if (backward_compatibility_v1) {
|
|
param_gethex_to_eol("00a4000002", 0, aSELECT_FILE_NDEF, sizeof(aSELECT_FILE_NDEF), &aSELECT_FILE_NDEF_n);
|
|
} else {
|
|
param_gethex_to_eol("00a4000c02", 0, aSELECT_FILE_NDEF, sizeof(aSELECT_FILE_NDEF), &aSELECT_FILE_NDEF_n);
|
|
}
|
|
memcpy(aSELECT_FILE_NDEF + aSELECT_FILE_NDEF_n, file_id, sizeof(file_id));
|
|
res = ExchangeAPDU14a(aSELECT_FILE_NDEF, aSELECT_FILE_NDEF_n + sizeof(file_id), activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "Selecting NDEF file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// read first 2 bytes to get NDEF length
|
|
uint8_t aREAD_NDEF[30];
|
|
int aREAD_NDEF_n = 0;
|
|
param_gethex_to_eol("00b0000002", 0, aREAD_NDEF, sizeof(aREAD_NDEF), &aREAD_NDEF_n);
|
|
res = ExchangeAPDU14a(aREAD_NDEF, aREAD_NDEF_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "reading NDEF file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint16_t ndef_size = (response[0] << 8) + response[1];
|
|
uint16_t offset = 2;
|
|
|
|
uint8_t *ndef_file = calloc(ndef_size, sizeof(uint8_t));
|
|
if (ndef_file == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
DropField();
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
if (ndef_size + offset > 0xFFFF) {
|
|
PrintAndLogEx(ERR, "NDEF size abnormally large in CmdHF14ANdef(). Aborting...\n");
|
|
free(ndef_file);
|
|
DropField();
|
|
return PM3_EOVFLOW;
|
|
}
|
|
|
|
for (size_t i = offset; i < ndef_size + offset; i += max_rapdu_size) {
|
|
size_t segment_size = max_rapdu_size < ndef_size + offset - i ? max_rapdu_size : ndef_size + offset - i;
|
|
|
|
keep_field_on = i < ndef_size + offset - max_rapdu_size;
|
|
aREAD_NDEF_n = 0;
|
|
param_gethex_to_eol("00b00000", 0, aREAD_NDEF, sizeof(aREAD_NDEF), &aREAD_NDEF_n);
|
|
aREAD_NDEF[2] = i >> 8;
|
|
aREAD_NDEF[3] = i & 0xFF;
|
|
|
|
// Segment_size is stuffed into a single-byte field below ... so error out if overflows
|
|
if (segment_size > 0xFFu) {
|
|
PrintAndLogEx(ERR, "Segment size too large (0x%zx > 0xFF)", segment_size);
|
|
DropField();
|
|
free(ndef_file);
|
|
return PM3_EOVFLOW;
|
|
}
|
|
aREAD_NDEF[4] = segment_size;
|
|
|
|
res = ExchangeAPDU14a(aREAD_NDEF, aREAD_NDEF_n + 1, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
free(ndef_file);
|
|
return res;
|
|
}
|
|
|
|
sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
PrintAndLogEx(ERR, "reading NDEF file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
free(ndef_file);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (resplen != segment_size + 2) {
|
|
PrintAndLogEx(ERR, "reading NDEF file failed, expected %zu bytes, got %i bytes.", segment_size, resplen - 2);
|
|
DropField();
|
|
free(ndef_file);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
memcpy(ndef_file + (i - offset), response, segment_size);
|
|
}
|
|
|
|
if (verbose2) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("NDEF raw") " ----------------");
|
|
print_buffer(ndef_file, ndef_size, 1);
|
|
}
|
|
|
|
NDEFRecordsDecodeAndPrint(ndef_file, ndef_size, verbose);
|
|
|
|
pm3_save_dump(filename, ndef_file, ndef_size, jsfNDEF);
|
|
|
|
if (verbose == false) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf 14a ndefread -v`") " for more details");
|
|
} else {
|
|
if (verbose2 == false) {
|
|
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf 14a ndefread -vv`") " for more details");
|
|
}
|
|
}
|
|
|
|
free(ndef_file);
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHF14ANdefFormat(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a ndefformat",
|
|
"Format ISO14443-a Tag as a NFC tag with Data Exchange Format (NDEF)",
|
|
"hf 14a ndefformat\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
CLIParserFree(ctx);
|
|
|
|
if (g_session.pm3_present == false)
|
|
return PM3_ENOTTY;
|
|
|
|
bool activate_field = true;
|
|
bool keep_field_on = false;
|
|
uint8_t response[PM3_CMD_DATA_SIZE];
|
|
int resplen = 0;
|
|
|
|
SetAPDULogging(false);
|
|
|
|
// step 1 - Select NDEF Tag application
|
|
uint8_t aSELECT_AID[80];
|
|
int aSELECT_AID_n = 0;
|
|
param_gethex_to_eol("00a4040007d276000085010100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n);
|
|
int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
|
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (resplen < 2) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
bool have_application = true;
|
|
uint16_t sw = get_sw(response, resplen);
|
|
if (sw != ISO7816_OK) {
|
|
have_application = false;
|
|
PrintAndLogEx(INFO, "no NDEF application found");
|
|
} else {
|
|
PrintAndLogEx(INFO, "found ndef application");
|
|
}
|
|
|
|
|
|
// setup desfire authentication context
|
|
uint8_t empty_key[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
DesfireContext_t dctx;
|
|
dctx.secureChannel = DACNone;
|
|
DesfireSetKey(&dctx, 0, T_DES, empty_key);
|
|
DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0);
|
|
DesfireSetCommandSet(&dctx, DCCNativeISO);
|
|
DesfireSetCommMode(&dctx, DCMPlain);
|
|
|
|
// step 1 - create application
|
|
if (have_application == false) {
|
|
// "hf mfdes createapp --aid 000001 --fid E110 --ks1 0B --ks2 A1 --dfhex D2760000850101 -t des -n 0 -k 0000000000000000"
|
|
PrintAndLogEx(INFO, "creating NDEF application...");
|
|
|
|
// authenticae first to AID 00 00 00
|
|
res = DesfireSelectAndAuthenticateEx(&dctx, DACEV1, 0x000000, false, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
PrintAndLogEx(INFO, "failed empty auth..");
|
|
return res;
|
|
}
|
|
|
|
// create application
|
|
uint8_t dfname[] = {0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01};
|
|
uint8_t ks1 = 0x0B;
|
|
uint8_t ks2 = 0xA1; // bit FileID in ks2
|
|
uint32_t appid = 0x0000001;
|
|
uint16_t fileid = 0xE110;
|
|
uint8_t data[250] = {0};
|
|
size_t datalen = 0;
|
|
|
|
DesfireAIDUintToByte(appid, &data[0]);
|
|
data[3] = ks1;
|
|
data[4] = ks2;
|
|
Uint2byteToMemLe(&data[5], fileid);
|
|
memcpy(&data[7], dfname, sizeof(dfname));
|
|
datalen = 14;
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "---------------------------");
|
|
PrintAndLogEx(INFO, _CYAN_("Creating Application using:"));
|
|
PrintAndLogEx(INFO, "AID........... 0x%02X%02X%02X", data[2], data[1], data[0]);
|
|
PrintAndLogEx(INFO, "Key Set 1..... 0x%02X", data[3]);
|
|
PrintAndLogEx(INFO, "Key Set 2..... 0x%02X", data[4]);
|
|
PrintAndLogEx(INFO, "ISO file ID... %s", (data[4] & 0x20) ? "enabled" : "disabled");
|
|
if ((data[4] & 0x20)) {
|
|
PrintAndLogEx(INFO, "ISO file ID... 0x%04X", MemLeToUint2byte(&data[5]));
|
|
PrintAndLogEx(INFO, "DF Name[%02d] %s | %s\n", 7, sprint_ascii(dfname, sizeof(dfname)), sprint_hex(dfname, sizeof(dfname)));
|
|
}
|
|
PrintKeySettings(data[3], data[4], true, true);
|
|
PrintAndLogEx(INFO, "---------------------------");
|
|
}
|
|
|
|
res = DesfireCreateApplication(&dctx, data, datalen);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Desfire CreateApplication command " _RED_("error") ". Result: %d", res);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Desfire application %06x successfully " _GREEN_("created"), appid);
|
|
|
|
|
|
// step 2 - create capability container (CC File)
|
|
|
|
// authenticae to the new AID 00 00 01
|
|
uint8_t aes_key[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
dctx.secureChannel = DACNone;
|
|
DesfireSetKey(&dctx, 0, T_AES, aes_key);
|
|
DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0);
|
|
DesfireSetCommandSet(&dctx, DCCNativeISO);
|
|
DesfireSetCommMode(&dctx, DCMPlain);
|
|
res = DesfireSelectAndAuthenticateEx(&dctx, DACEV1, 0x000001, false, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
PrintAndLogEx(INFO, "failed aid auth..");
|
|
return res;
|
|
}
|
|
|
|
// hf mfdes createfile --aid 000001 --fid 01 --isofid E103 --amode plain --size 00000F
|
|
// --rrights free --wrights key0 --rwrights key0 --chrights key0
|
|
// -n 0 -t aes -k 00000000000000000000000000000000 -m plain
|
|
uint8_t fid = 0x01;
|
|
uint16_t isofid = 0xE103;
|
|
uint32_t fsize = 0x0F;
|
|
uint8_t filetype = 0x00; // standard file
|
|
|
|
// file access mode: plain 0x00
|
|
// read access: free 0x0E
|
|
// write access: key0 0x00
|
|
// r/w access: key0 0x00
|
|
// change access: key0 0x00
|
|
memset(data, 0x00, sizeof(data));
|
|
datalen = 0;
|
|
|
|
data[0] = fid;
|
|
data[1] = isofid & 0xff;
|
|
data[2] = (isofid >> 8) & 0xff;
|
|
datalen = 3;
|
|
|
|
uint8_t *settings = &data[datalen];
|
|
settings[0] = 0x00;
|
|
datalen++;
|
|
|
|
DesfireEncodeFileAcessMode(&settings[1], 0x0E, 0x00, 0x00, 0x00) ;
|
|
datalen += 2;
|
|
|
|
Uint3byteToMemLe(&data[datalen], fsize);
|
|
datalen += 3;
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "App: %06x. File num: 0x%02x type: 0x%02x data[%zu]: %s", appid, data[0], filetype, datalen, sprint_hex(data, datalen));
|
|
}
|
|
|
|
DesfirePrintCreateFileSettings(filetype, data, datalen);
|
|
|
|
res = DesfireCreateFile(&dctx, filetype, data, datalen, true); // check length only if we dont use raw mode
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Desfire CreateFile command " _RED_("error") ". Result: %d", res);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "%s file %02x in the app %06x created " _GREEN_("successfully"), GetDesfireFileType(filetype), data[0], appid);
|
|
|
|
|
|
|
|
// hf mfdes write --aid 000001 --fid 01 -d 000F20003B00340406E10400FF00FF
|
|
// -n 0 -t aes -k 00000000000000000000000000000000 -m plain
|
|
res = DesfireSelectAndAuthenticateEx(&dctx, DACEV1, 0x000001, false, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
PrintAndLogEx(INFO, "failed aid auth..");
|
|
return res;
|
|
}
|
|
|
|
uint8_t fnum = 0x01;
|
|
uint32_t offset = 0;
|
|
uint8_t cc_data[] = {0x00, 0x0F, 0x20, 0x00, 0x3B, 0x00, 0x34, 0x04, 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00};
|
|
|
|
res = DesfireWriteFile(&dctx, fnum, offset, sizeof(cc_data), cc_data);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Desfire WriteFile command " _RED_("error") ". Result: %d", res);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Write data file %02x " _GREEN_("success"), fnum);
|
|
}
|
|
|
|
|
|
|
|
// step 3 - create NDEF record file
|
|
// hf mfdes write --aid 000001 --fid 02 -d 000CD1010855016E78702E636F6DFE
|
|
// -n 0 -t aes -k 00000000000000000000000000000000 -m plain
|
|
|
|
fid = 0x02;
|
|
isofid = 0xE104;
|
|
fsize = 0xFF;
|
|
filetype = 0x00; // standard file
|
|
|
|
// file access mode: plain 0x00
|
|
// read access: free 0x0E
|
|
// write access: key0 0x00
|
|
// r/w access: key0 0x00
|
|
// change access: key0 0x00
|
|
memset(data, 0x00, sizeof(data));
|
|
datalen = 0;
|
|
|
|
data[0] = fid;
|
|
data[1] = isofid & 0xff;
|
|
data[2] = (isofid >> 8) & 0xff;
|
|
datalen = 3;
|
|
|
|
settings = &data[datalen];
|
|
settings[0] = 0x00;
|
|
datalen++;
|
|
|
|
DesfireEncodeFileAcessMode(&settings[1], 0x0E, 0x00, 0x00, 0x00) ;
|
|
datalen += 2;
|
|
|
|
Uint3byteToMemLe(&data[datalen], fsize);
|
|
datalen += 3;
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "App: %06x. File num: 0x%02x type: 0x%02x data[%zu]: %s", appid, data[0], filetype, datalen, sprint_hex(data, datalen));
|
|
}
|
|
|
|
DesfirePrintCreateFileSettings(filetype, data, datalen);
|
|
|
|
res = DesfireCreateFile(&dctx, filetype, data, datalen, true); // check length only if we dont use raw mode
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Desfire CreateFile command " _RED_("error") ". Result: %d", res);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "%s file %02x in the app %06x created " _GREEN_("successfully"), GetDesfireFileType(filetype), data[0], appid);
|
|
|
|
DropField();
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHF14ANdefWrite(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a ndefwrite",
|
|
"Write raw NDEF hex bytes to tag. This commands assumes tag already been NFC/NDEF formatted.\n",
|
|
"hf 14a ndefwrite -d 0300FE -> write empty record to tag\n"
|
|
"hf 14a ndefwrite -f myfilename\n"
|
|
"hf 14a ndefwrite -d 003fd1023a53709101195405656e2d55534963656d616e2054776974746572206c696e6b5101195502747769747465722e636f6d2f686572726d616e6e31303031\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("d", NULL, "<hex>", "raw NDEF hex bytes"),
|
|
arg_str0("f", "file", "<fn>", "write raw NDEF file to tag"),
|
|
arg_lit0("p", NULL, "fix NDEF record headers / terminator block if missing"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
uint8_t raw[256] = {0};
|
|
int rawlen = 0;
|
|
CLIGetHexWithReturn(ctx, 1, raw, &rawlen);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool fix_msg = arg_get_lit(ctx, 3);
|
|
bool verbose = arg_get_lit(ctx, 4);
|
|
CLIParserFree(ctx);
|
|
|
|
if (g_session.pm3_present == false) {
|
|
return PM3_ENOTTY;
|
|
}
|
|
|
|
if ((rawlen && fnlen) || (rawlen == 0 && fnlen == 0)) {
|
|
PrintAndLogEx(WARNING, "Please specify either raw hex or filename");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int res = PM3_SUCCESS;
|
|
int32_t bytes = rawlen;
|
|
|
|
// read dump file
|
|
if (fnlen) {
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 0;
|
|
res = pm3_load_dump(filename, (void **)&dump, &bytes_read, sizeof(raw));
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
memcpy(raw, dump, bytes_read);
|
|
bytes = bytes_read;
|
|
free(dump);
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Num of bytes... %i (raw %i)", bytes, rawlen);
|
|
}
|
|
|
|
// Has raw bytes ndef message header?bytes
|
|
switch (raw[0]) {
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0xFD:
|
|
case 0xFE:
|
|
break;
|
|
default: {
|
|
if (fix_msg == false) {
|
|
PrintAndLogEx(WARNING, "raw NDEF message doesn't have a proper header, continuing...");
|
|
} else {
|
|
if (bytes + 2 > sizeof(raw)) {
|
|
PrintAndLogEx(WARNING, "no room for header, exiting...");
|
|
return PM3_EMALLOC;
|
|
}
|
|
uint8_t tmp_raw[256];
|
|
memcpy(tmp_raw, raw, sizeof(tmp_raw));
|
|
raw[0] = 0x00;
|
|
raw[1] = bytes;
|
|
memcpy(raw + 2, tmp_raw, sizeof(raw) - 2);
|
|
bytes += 2;
|
|
PrintAndLogEx(SUCCESS, "Added generic message header (0x03)");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Has raw bytes ndef a terminator block?
|
|
if (raw[bytes - 1] != 0xFE) {
|
|
if (fix_msg == false) {
|
|
PrintAndLogEx(WARNING, "raw NDEF message doesn't have a terminator block, continuing...");
|
|
} else {
|
|
|
|
if (bytes + 1 > sizeof(raw)) {
|
|
PrintAndLogEx(WARNING, "no room for terminator block, exiting...");
|
|
return PM3_EMALLOC;
|
|
}
|
|
raw[bytes] = 0xFE;
|
|
bytes++;
|
|
PrintAndLogEx(SUCCESS, "Added terminator block (0xFE)");
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Num of Bytes... %u", bytes);
|
|
print_buffer(raw, bytes, 0);
|
|
}
|
|
|
|
|
|
// setup desfire authentication context
|
|
// authenticae to the new AID 00 00 01
|
|
uint8_t aes_key[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
DesfireContext_t dctx;
|
|
dctx.secureChannel = DACNone;
|
|
DesfireSetKey(&dctx, 0, T_AES, aes_key);
|
|
DesfireSetKdf(&dctx, MFDES_KDF_ALGO_NONE, NULL, 0);
|
|
DesfireSetCommandSet(&dctx, DCCNativeISO);
|
|
DesfireSetCommMode(&dctx, DCMPlain);
|
|
res = DesfireSelectAndAuthenticateEx(&dctx, DACEV1, 0x000001, false, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
DropField();
|
|
PrintAndLogEx(INFO, "failed aid auth..");
|
|
return res;
|
|
}
|
|
|
|
// write ndef file
|
|
|
|
// hf mfdes write --aid 000002 --fid 02 -
|
|
// -n 0 -t aes -k 00000000000000000000000000000000 -m plain
|
|
uint8_t fnum = 0x02;
|
|
uint32_t offset = 0;
|
|
|
|
res = DesfireWriteFile(&dctx, fnum, offset, bytes, raw);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Desfire WriteFile command " _RED_("error") ". Result: %d", res);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Write data file %02x " _GREEN_("success"), fnum);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Simulate ISO/IEC 14443 type A tag with 4,7 or 10 byte UID, and filter for AID Values
|
|
* These AID Values can be responded to and include extra APDU commands after verification
|
|
*/
|
|
|
|
int CmdHF14AAIDSim(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf 14a simaid",
|
|
"Simulate ISO/IEC 14443 type A tag with 4,7 or 10 byte UID, and filter for AID Values\n"
|
|
"These AID Values can be responded to and include extra APDU commands on GetData after response\n",
|
|
"hf 14a simaid -t 3 -> MIFARE Desfire\n"
|
|
"hf 14a simaid -t 4 -> ISO/IEC 14443-4\n"
|
|
"hf 14a simaid -t 11 -> Javacard (JCOP)\n"
|
|
"hf 14a simaid -t 3 --aid a000000000000000000000 --selectaid_response 9000 --getdata_response 9000 -> Custom AID and responses\n"
|
|
"hf 14a simaid -t 3 --ats 0578817222 --selectaid_response 01009000 --getdata_response 86009000 -> Custom ATS and responses\n"
|
|
"hf 14a simaid -t 3 --ats 0578817222 -x -> Enumerate AID Values\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("t", "type", "<1-12> ", "Simulation type to use"),
|
|
arg_str0("u", "uid", "<hex>", "<4|7|10> hex bytes UID"),
|
|
arg_str0("r", "ats", "<hex>", "<0-20> hex bytes ATS"),
|
|
arg_str0("a", "aid", "<hex>", "<0-30> hex bytes for AID to respond to (Default: A000000000000000000000)"),
|
|
arg_str0("e", "selectaid_response", "<hex>", "<0-256> hex bytes for APDU Response to AID Select (Default: 9000)"),
|
|
arg_str0("p", "getdata_response", "<hex>", "<0-100> hex bytes for APDU Response to Get Data request after AID (Default: 9000)"),
|
|
arg_lit0("x", "enumerate", "Enumerate all AID values via returning Not Found and print them to console "),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int uid_len = 0;
|
|
int ats_len = 0;
|
|
int aid_len = 0;
|
|
int selectaid_response_len = 0;
|
|
int getdata_response_len = 0;
|
|
|
|
uint8_t uid[10] = {0};
|
|
uint8_t ats[20] = {0};
|
|
uint8_t aid[30] = {0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t default_aid_len = 11;
|
|
uint8_t selectaid_response[256] = {0x90, 0x00};
|
|
uint8_t default_selectaid_response_len = 2;
|
|
uint8_t getdata_response[100] = {0x90, 0x00};
|
|
uint8_t default_getdata_response_len = 2;
|
|
|
|
int tagtype = arg_get_int_def(ctx, 1, 1);
|
|
CLIGetHexWithReturn(ctx, 2, uid, &uid_len);
|
|
CLIGetHexWithReturn(ctx, 3, ats, &ats_len);
|
|
CLIGetHexWithReturn(ctx, 4, aid, &aid_len);
|
|
CLIGetHexWithReturn(ctx, 5, selectaid_response, &selectaid_response_len);
|
|
CLIGetHexWithReturn(ctx, 6, getdata_response, &getdata_response_len);
|
|
bool enumerate = arg_get_lit(ctx, 7);
|
|
CLIParserFree(ctx);
|
|
|
|
// default value fill for the AID, selectaid_response, and getdata_response
|
|
if (aid_len == 0) {
|
|
aid_len = default_aid_len;
|
|
}
|
|
|
|
if (selectaid_response_len == 0) {
|
|
selectaid_response_len = default_selectaid_response_len;
|
|
}
|
|
if (getdata_response_len == 0) {
|
|
getdata_response_len = default_getdata_response_len;
|
|
}
|
|
|
|
uint16_t flags = 0;
|
|
bool useUIDfromEML = true;
|
|
|
|
if (uid_len > 0) {
|
|
FLAG_SET_UID_IN_DATA(flags, uid_len);
|
|
if (IS_FLAG_UID_IN_EMUL(flags)) {
|
|
PrintAndLogEx(ERR, "Please specify a 4, 7, or 10 byte UID");
|
|
return PM3_EINVARG;
|
|
}
|
|
PrintAndLogEx(SUCCESS, "Emulating " _YELLOW_("ISO/IEC 14443 type A tag")" with " _GREEN_("%d byte UID (%s)"), uid_len, sprint_hex(uid, uid_len));
|
|
useUIDfromEML = false;
|
|
}
|
|
|
|
if (ats_len > sizeof(ats)) {
|
|
PrintAndLogEx(ERR, "Provided ATS too long");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (aid_len > sizeof(aid)) {
|
|
PrintAndLogEx(ERR, "Provided AID too long");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (selectaid_response_len > sizeof(selectaid_response)) {
|
|
PrintAndLogEx(ERR, "Provided SelectAID response too long");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (getdata_response_len > sizeof(getdata_response)) {
|
|
PrintAndLogEx(ERR, "Provided GetData response too long");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (ats_len > 0) {
|
|
flags |= FLAG_ATS_IN_DATA;
|
|
}
|
|
|
|
if (enumerate) {
|
|
flags |= FLAG_ENUMERATE_AID;
|
|
}
|
|
|
|
if (tagtype > 12) {
|
|
PrintAndLogEx(ERR, "Undefined tag %d", tagtype);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (useUIDfromEML) {
|
|
FLAG_SET_UID_IN_EMUL(flags);
|
|
}
|
|
|
|
struct {
|
|
uint8_t tagtype;
|
|
uint16_t flags;
|
|
uint8_t uid[10];
|
|
uint8_t ats[20];
|
|
uint8_t aid[30];
|
|
uint8_t selectaid_response[256];
|
|
uint8_t getdata_response[100];
|
|
uint32_t ats_len;
|
|
uint32_t aid_len;
|
|
uint32_t selectaid_response_len;
|
|
uint32_t getdata_response_len;
|
|
} PACKED payload;
|
|
|
|
payload.tagtype = tagtype;
|
|
payload.flags = flags;
|
|
|
|
// Copy data to payload
|
|
memcpy(payload.uid, uid, uid_len);
|
|
memcpy(payload.ats, ats, ats_len);
|
|
memcpy(payload.aid, aid, aid_len);
|
|
memcpy(payload.selectaid_response, selectaid_response, selectaid_response_len);
|
|
memcpy(payload.getdata_response, getdata_response, getdata_response_len);
|
|
|
|
// copy the lengths data to the payload
|
|
payload.ats_len = ats_len;
|
|
payload.aid_len = aid_len;
|
|
payload.selectaid_response_len = selectaid_response_len;
|
|
payload.getdata_response_len = getdata_response_len;
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ISO14443A_SIM_AID, (uint8_t *)&payload, sizeof(payload));
|
|
PacketResponseNG resp = {0};
|
|
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort simulation");
|
|
bool keypress = kbd_enter_pressed();
|
|
while (keypress == false) {
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == false) {
|
|
continue;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) {
|
|
break;
|
|
}
|
|
|
|
keypress = kbd_enter_pressed();
|
|
}
|
|
|
|
if (keypress) {
|
|
if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) {
|
|
// inform device to break the sim loop since client has exited
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
}
|
|
}
|
|
|
|
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace list -t 14a")"` to view captured tracelog");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace save -h") "` to save tracelog for later analysing");
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
|
|
static command_t CommandTable[] = {
|
|
{"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("General") " -----------------------"},
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"list", CmdHF14AList, AlwaysAvailable, "List ISO 14443-a history"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "---------------------- " _CYAN_("Operations") " ---------------------"},
|
|
{"antifuzz", CmdHF14AAntiFuzz, IfPm3Iso14443a, "Fuzzing the anticollision phase. Warning! Readers may react strange"},
|
|
{"config", CmdHf14AConfig, IfPm3Iso14443a, "Configure 14a settings (use with caution)"},
|
|
{"cuids", CmdHF14ACUIDs, IfPm3Iso14443a, "Collect n>0 ISO14443-a UIDs in one go"},
|
|
{"info", CmdHF14AInfo, IfPm3Iso14443a, "Tag information"},
|
|
{"sim", CmdHF14ASim, IfPm3Iso14443a, "Simulate ISO 14443-a tag"},
|
|
{"simaid", CmdHF14AAIDSim, IfPm3Iso14443a, "Simulate ISO 14443-a AID Selection"},
|
|
{"sniff", CmdHF14ASniff, IfPm3Iso14443a, "sniff ISO 14443-a traffic"},
|
|
{"raw", CmdHF14ACmdRaw, IfPm3Iso14443a, "Send raw hex data to tag"},
|
|
{"reader", CmdHF14AReader, IfPm3Iso14443a, "Act like an ISO14443-a reader"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "------------------------- " _CYAN_("APDU") " -------------------------"},
|
|
{"apdu", CmdHF14AAPDU, IfPm3Iso14443a, "Send ISO 14443-4 APDU to tag"},
|
|
{"apdufind", CmdHf14AFindapdu, IfPm3Iso14443a, "Enumerate APDUs - CLA/INS/P1P2"},
|
|
{"chaining", CmdHF14AChaining, IfPm3Iso14443a, "Control ISO 14443-4 input chaining"},
|
|
{"-----------", CmdHelp, IfPm3Iso14443a, "------------------------- " _CYAN_("NDEF") " -------------------------"},
|
|
{"ndefformat", CmdHF14ANdefFormat, IfPm3Iso14443a, "Format ISO 14443-A as NFC Type 4 tag"},
|
|
{"ndefread", CmdHF14ANdefRead, IfPm3Iso14443a, "Read an NDEF file from ISO 14443-A Type 4 tag"},
|
|
{"ndefwrite", CmdHF14ANdefWrite, IfPm3Iso14443a, "Write NDEF records to ISO 14443-A tag"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
static int CmdHelp(const char *Cmd) {
|
|
(void)Cmd; // Cmd is not used so far
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHF14A(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|