From 8bc080b3b8a38ab43d3a6661daa9f0629a8c4dfb Mon Sep 17 00:00:00 2001 From: Sanduuz Date: Fri, 24 Jul 2026 11:53:14 +0300 Subject: [PATCH] Added support for emulating ST25TA tag (IKEA Rothult) with custom NDEF response --- armsrc/appmain.c | 8 ++- armsrc/iso14443a.c | 28 ++++++-- armsrc/iso14443a.h | 3 +- client/src/cmdhf14a.c | 3 + client/src/cmdhfst25ta.c | 142 ++++++++++++++++++++++++++++++++++++++- include/pm3_cmd.h | 3 + 6 files changed, 178 insertions(+), 9 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 82ff672e0..ddb6746b1 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -26,6 +26,9 @@ #include "bwm_uart_at32.h" #include "bwm_wifi.h" #endif +#include "clocks.h" +#include "pm3_cmd.h" +#include "usb_cdc.h" #include "proxmark3_arm.h" #include "dbprint.h" #include "pmflash.h" @@ -2144,13 +2147,16 @@ static void PacketReceived(PacketCommandNG *packet) { uint8_t ulauth_1a1[16]; uint8_t ulauth_1a2[16]; bool ulauth_1a2_mirror; + uint16_t st25ta_ndef_len; + uint8_t st25ta_ndef[ST25TA_SIM_NDEF_MAX]; } PACKED; struct p *payload = (struct p *) packet->data.asBytes; SimulateIso14443aTagEx(payload->tagtype, payload->flags, payload->uid, payload->exitAfter, payload->rats, sizeof(payload->rats), payload->ulauth_1a1, payload->ulauth_1a1_len, payload->ulauth_1a2, payload->ulauth_1a2_len, - payload->ulauth_1a2_mirror + payload->ulauth_1a2_mirror, + payload->st25ta_ndef, payload->st25ta_ndef_len ); // ## Simulate iso14443a tag - pass tag type & UID break; } diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 8e7f0afe2..30b321990 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -19,6 +19,7 @@ //----------------------------------------------------------------------------- #include "iso14443a.h" +#include "pm3_cmd.h" #include "string.h" #include "proxmark3_arm.h" #include "cmd.h" @@ -1714,14 +1715,15 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // 'hf 14a sim' //----------------------------------------------------------------------------- void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uint8_t exitAfterNReads) { - SimulateIso14443aTagEx(tagType, flags, useruid, exitAfterNReads, NULL, 0, NULL, 0, NULL, 0, false); + SimulateIso14443aTagEx(tagType, flags, useruid, exitAfterNReads, NULL, 0, NULL, 0, NULL, 0, false, NULL, 0); } void SimulateIso14443aTagEx(uint8_t tagType, uint16_t flags, uint8_t *useruid, uint8_t exitAfterNReads, uint8_t *ats, size_t ats_len, uint8_t *ulauth_1a1, uint8_t ulauth_1a1_len, uint8_t *ulauth_1a2, uint8_t ulauth_1a2_len, - bool ulauth_1a2_mirror) { + bool ulauth_1a2_mirror, + const uint8_t *st25ta_ndef, uint16_t st25ta_ndef_len) { #define ATTACK_KEY_COUNT 16 #define ULC_TAG_NONCE "\x01\x02\x03\x04\x05\x06\x07\x08" @@ -1754,8 +1756,8 @@ void SimulateIso14443aTagEx(uint8_t tagType, uint16_t flags, uint8_t *useruid, u // Allocate 512 bytes for the dynamic modulation, created when the reader queries for it // Such a response is less time critical, so we can prepare them on the fly -#define DYNAMIC_RESPONSE_BUFFER_SIZE 64 -#define DYNAMIC_MODULATION_BUFFER_SIZE 512 +#define DYNAMIC_RESPONSE_BUFFER_SIZE (ST25TA_SIM_NDEF_MAX + 4) +#define DYNAMIC_MODULATION_BUFFER_SIZE 4096 uint8_t *dynamic_response_buffer = BigBuf_calloc(DYNAMIC_RESPONSE_BUFFER_SIZE); if (dynamic_response_buffer == NULL) { @@ -2429,10 +2431,24 @@ void SimulateIso14443aTagEx(uint8_t tagType, uint16_t flags, uint8_t *useruid, u if (tagType == 10) { // we replay 90 00 for all commands but the read bin and we deny the verify cmd. + static const uint8_t default_st25ta_ndef[] = { + 0x00, 0x1b, 0xd1, 0x01, 0x17, 0x54, 0x02, 0x7a, + 0x68, 0xa2, 0x34, 0xcb, 0xd0, 0xe2, 0x03, 0xc7, + 0x3e, 0x62, 0x0b, 0xe8, 0xc6, 0x3c, 0x85, 0x2c, + 0xc5, 0x31, 0x31, 0x31, 0x32, 0x90, 0x00 + }; + const uint8_t *ndef = default_st25ta_ndef; + uint16_t ndef_len = sizeof(default_st25ta_ndef); + + if (st25ta_ndef != NULL && st25ta_ndef_len > 0 && st25ta_ndef_len <= ST25TA_SIM_NDEF_MAX) { + ndef = st25ta_ndef; + ndef_len = st25ta_ndef_len; + } + if (memcmp("\x02\xa2\xb0\x00\x00\x1d\x51\x69", receivedCmd, 8) == 0) { dynamic_response_info.response[0] = receivedCmd[0]; - memcpy(dynamic_response_info.response + 1, "\x00\x1b\xd1\x01\x17\x54\x02\x7a\x68\xa2\x34\xcb\xd0\xe2\x03\xc7\x3e\x62\x0b\xe8\xc6\x3c\x85\x2c\xc5\x31\x31\x31\x32\x90\x00", 31); - dynamic_response_info.response_n = 32; + memcpy(dynamic_response_info.response + 1, ndef, ndef_len); + dynamic_response_info.response_n = ndef_len + 1; } else if (memcmp("\x02\x00\x20\x00\x01\x00\x6e\xa9", receivedCmd, 8) == 0) { dynamic_response_info.response[0] = receivedCmd[0]; dynamic_response_info.response[1] = 0x63; diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index 559ca7827..a644451ea 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -148,7 +148,8 @@ void SimulateIso14443aTagEx(uint8_t tagType, uint16_t flags, uint8_t *useruid, u uint8_t *ats, size_t ats_len, uint8_t *ulauth_1a1, uint8_t ulauth_1a1_len, uint8_t *ulauth_1a2, uint8_t ulauth_1a2_len, - bool ulauth_1a2_mirror); + bool ulauth_1a2_mirror, + const uint8_t *st25ta_ndef, uint16_t st25ta_ndef_len); void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, uint8_t *ats, size_t ats_len, uint8_t *aid, size_t aid_len, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index a720aece4..71a71317c 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1037,8 +1037,11 @@ int CmdHF14ASim(const char *Cmd) { uint8_t ulauth_1a1[16]; uint8_t ulauth_1a2[16]; bool ulauth_1a2_mirror; + uint16_t st25ta_ndef_len; + uint8_t st25ta_ndef[ST25TA_SIM_NDEF_MAX]; } PACKED payload; + memset(&payload, 0x00, sizeof(payload)); payload.tagtype = tagtype; payload.flags = flags; payload.exitAfter = exitAfterNReads; diff --git a/client/src/cmdhfst25ta.c b/client/src/cmdhfst25ta.c index 76c32d154..ff85a3a87 100644 --- a/client/src/cmdhfst25ta.c +++ b/client/src/cmdhfst25ta.c @@ -26,6 +26,7 @@ #include "cliparser.h" #include "crc16.h" #include "cmdhf14a.h" +#include "pm3_cmd.h" #include "protocols.h" // definitions of ISO14A/7816 protocol #include "iso7816/apduinfo.h" // GetAPDUCodeDescription #include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint @@ -34,6 +35,8 @@ #include "protocols.h" // ISO7816 APDU return codes #include "crypto/libpcrypto.h" // ecdsa #include "crypto/originality.h" +#include "ui.h" +#include "util.h" // kbd_enter_pressed #define TIMEOUT 2000 @@ -335,22 +338,122 @@ static int CmdHFST25TAInfo(const char *Cmd) { return infoHFST25TA(); } +static int st25ta_normalize_sim_ndef(const uint8_t *src, size_t src_len, uint8_t *dst, uint16_t *dst_len) { + if (src == NULL || src_len == 0 || dst == NULL || dst_len == NULL) { + return PM3_EINVARG; + } + + if (src_len >= 4 && src[src_len - 2] == 0x90 && src[src_len - 1] == 0x00) { + if (src_len > ST25TA_SIM_NDEF_MAX) { + PrintAndLogEx(ERR, "NDEF response too large. Max %u bytes", ST25TA_SIM_NDEF_MAX); + return PM3_EINVARG; + } + memcpy(dst, src, src_len); + *dst_len = src_len; + return PM3_SUCCESS; + } + + if (src_len >= 2) { + uint16_t nlen = (src[0] << 8) | src[1]; + if (nlen + 2 == src_len) { + if (src_len + 2 > ST25TA_SIM_NDEF_MAX) { + PrintAndLogEx(ERR, "NDEF response too large. Max %u bytes", ST25TA_SIM_NDEF_MAX); + return PM3_EINVARG; + } + memcpy(dst, src, src_len); + dst[src_len] = 0x90; + dst[src_len + 1] = 0x00; + *dst_len = src_len + 2; + return PM3_SUCCESS; + } + } + + if (src_len + 4 > ST25TA_SIM_NDEF_MAX) { + PrintAndLogEx(ERR, "NDEF response too large. Max %u bytes", ST25TA_SIM_NDEF_MAX); + return PM3_EINVARG; + } + + dst[0] = (src_len >> 8) & 0xff; + dst[1] = src_len & 0xff; + memcpy(dst + 2, src, src_len); + dst[src_len + 2] = 0x90; + dst[src_len + 3] = 0x00; + *dst_len = src_len + 4; + return PM3_SUCCESS; +} + +static int st25ta_simulate_with_ndef(const uint8_t *uid, const uint8_t *ndef, uint16_t ndef_len) { + 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]; + bool ulauth_1a2_mirror; + uint16_t st25ta_ndef_len; + uint8_t st25ta_ndef[ST25TA_SIM_NDEF_MAX]; + } PACKED payload; + + memset(&payload, 0x00, sizeof(payload)); + payload.tagtype = 10; + payload.flags = 0; + FLAG_SET_UID_IN_DATA(payload.flags, 7); + memcpy(payload.uid, uid, 7); + + if (ndef != NULL && ndef_len > 0) { + payload.st25ta_ndef_len = ndef_len; + memcpy(payload.st25ta_ndef, ndef, ndef_len); + } + + clearCommandBuffer(); + SendCommandNG(CMD_HF_ISO14443A_SIMULATE, (uint8_t *)&payload, sizeof(payload)); + + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to abort simulation"); + PacketResponseNG resp = {0}; + bool keypress = kbd_enter_pressed(); + while (keypress == false) { + keypress = kbd_enter_pressed(); + + if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == false) { + continue; + } + + break; + } + + return resp.status; +} + static int CmdHFST25TASim(const char *Cmd) { int uidlen = 0; uint8_t uid[7] = {0}; + int ndef_hex_len = 0; + uint8_t ndef_hex[ST25TA_SIM_NDEF_MAX] = {0}; CLIParserContext *ctx; CLIParserInit(&ctx, "hf st25ta sim", "Emulating ST25TA512B tag with 7 byte UID", - "hf st25ta sim -u 02E2007D0FCA4C\n"); + "hf st25ta sim -u 02E2007D0FCA4C\n" + "hf st25ta sim -u 02E2007D0FCA4C --ndef 001BD1011754027A68A234CBD0E203C73E620BE8C63C852CC5313131329000\n" + "hf st25ta sim -u 02E2007D0FCA4C -f my-ndef.bin\n"); void *argtable[] = { arg_param_begin, arg_str1("u", "uid", "", "7 byte UID"), + arg_str0(NULL, "ndef", "", "NDEF data or full READ BINARY response"), + arg_str0("f", "file", "", "load NDEF data or full READ BINARY response from file"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); CLIGetHexWithReturn(ctx, 1, uid, &uidlen); + int ndef_res = CLIParamHexToBuf(arg_get_str(ctx, 2), ndef_hex, sizeof(ndef_hex), &ndef_hex_len); + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); CLIParserFree(ctx); if (uidlen != 7) { @@ -358,6 +461,43 @@ static int CmdHFST25TASim(const char *Cmd) { return PM3_EINVARG; } + if (ndef_res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Invalid NDEF hex"); + return ndef_res; + } + + if (ndef_hex_len > 0 && fnlen > 0) { + PrintAndLogEx(ERR, "Use either --ndef or -f, not both"); + return PM3_EINVARG; + } + + uint8_t ndef[ST25TA_SIM_NDEF_MAX] = {0}; + uint16_t ndef_len = 0; + + if (ndef_hex_len > 0) { + int res = st25ta_normalize_sim_ndef(ndef_hex, ndef_hex_len, ndef, &ndef_len); + if (res != PM3_SUCCESS) { + return res; + } + } else if (fnlen > 0) { + uint8_t *file_data = NULL; + size_t file_len = 0; + int res = loadFile_safe(filename, "", (void **)&file_data, &file_len); + if (res != PM3_SUCCESS) { + return res; + } + res = st25ta_normalize_sim_ndef(file_data, file_len, ndef, &ndef_len); + free(file_data); + if (res != PM3_SUCCESS) { + return res; + } + } + + if (ndef_len > 0) { + PrintAndLogEx(SUCCESS, "Emulating ST25TA512B with supplied NDEF response (%u bytes)", ndef_len); + return st25ta_simulate_with_ndef(uid, ndef, ndef_len); + } + char param[40]; snprintf(param, sizeof(param), "-t 10 -u %s", sprint_hex_inrow(uid, uidlen)); return CmdHF14ASim(param); diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 739c6b0c4..b33f23da3 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -974,6 +974,9 @@ typedef struct { #define CMD_UNKNOWN 0xFFFF +// Max NDEF record length for ST25TA simulation +#define ST25TA_SIM_NDEF_MAX 256 + // Mifare simulation flags // In interactive mode, we are expected to finish the operation with an ACK #define FLAG_INTERACTIVE 0x0001