From c6675018b3bd264d8f9342581f846d3f54d6a553 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:24:05 +0200 Subject: [PATCH] felica: use NG frames for commands --- armsrc/felica.c | 95 +++++++++++++++------ client/src/cmdhffelica.c | 179 ++++++++++++++++++++++++++------------- include/iso18.h | 14 +++ 3 files changed, 202 insertions(+), 86 deletions(-) diff --git a/armsrc/felica.c b/armsrc/felica.c index 7fb1c6053..69eece61f 100644 --- a/armsrc/felica.c +++ b/armsrc/felica.c @@ -62,14 +62,19 @@ static uint32_t iso18092_get_timeout(void) { return felica_timeout - (DELAY_AIR2ARM_AS_READER + DELAY_ARM2AIR_AS_READER) / (16 * 8) - 2; } -#ifndef FELICA_MAX_FRAME_SIZE -// 255 base length (max 254 data + 1 len byte) + 2 sync + 2 crc + 1 extra for safety -#define FELICA_MAX_FRAME_SIZE 260 +#ifndef FELICA_MAX_DATA_SIZE +// FeliCa length byte includes itself, so application level payload max is 254 bytes. +#define FELICA_MAX_DATA_SIZE 254 +#endif + +#ifndef FELICA_MAX_RF_FRAME_SIZE +// 255 base length (max 254 data + 1 len byte) + 2 sync + 2 crc + 1 extra for safety. +#define FELICA_MAX_RF_FRAME_SIZE 260 #endif //structure to hold outgoing NFC frame -static uint8_t frameSpace[FELICA_MAX_FRAME_SIZE]; +static uint8_t frameSpace[FELICA_MAX_RF_FRAME_SIZE]; //structure to hold incoming NFC frame, used for ISO/IEC 18092-compatible frames static struct { @@ -492,7 +497,7 @@ static void iso18092_setup(uint8_t fpga_minor_mode) { #endif // allocate command receive buffer BigBuf_free(); - FelicaFrameinit(BigBuf_calloc(FELICA_MAX_FRAME_SIZE)); + FelicaFrameinit(BigBuf_calloc(FELICA_MAX_RF_FRAME_SIZE)); felica_nexttransfertime = 2 * DELAY_ARM2AIR_AS_READER; // 418 // iso18092_set_timeout(2120); // 106 * 20ms maximum start-up time of card @@ -533,14 +538,30 @@ static void felica_reset_frame_mode(void) { //----------------------------------------------------------------------------- // RAW FeliCa commands. Send out commands and store answers. //----------------------------------------------------------------------------- -// arg0 FeliCa flags -// arg1 len of commandbytes -// d.asBytes command bytes to send void felica_sendraw(const PacketCommandNG *c) { - felica_command_t param = c->oldarg[0]; - size_t len = c->oldarg[1] & 0xffff; - uint32_t arg0; + felica_command_t param = 0; + size_t len = 0; + const uint8_t *payload = NULL; + + if (!c->ng) { + reply_ng(CMD_HF_FELICA_COMMAND, PM3_EINVARG, NULL, 0); + return; + } + if (c->length < sizeof(felica_raw_cmd_t)) { + reply_ng(CMD_HF_FELICA_COMMAND, PM3_EINVARG, NULL, 0); + return; + } + const felica_raw_cmd_t *request = (const felica_raw_cmd_t *)c->data.asBytes; + if ((size_t)request->rawlen > PM3_CMD_DATA_SIZE || FELICA_RAW_LEN(request->rawlen) > c->length) { + reply_ng(CMD_HF_FELICA_COMMAND, PM3_EINVARG, NULL, 0); + return; + } + + param = request->flags; + len = request->rawlen; + payload = request->raw; + bool do_connect = ((param & FELICA_CONNECT) == FELICA_CONNECT); bool no_disconnect = ((param & FELICA_NO_DISCONNECT) == FELICA_NO_DISCONNECT); @@ -560,26 +581,42 @@ void felica_sendraw(const PacketCommandNG *c) { // notify client selecting status. // if failed selecting, turn off antenna and quit. - felica_card_select_t card; - arg0 = felica_select_card(&card); - reply_mix(CMD_ACK, arg0, sizeof(card.uid), 0, &card, sizeof(felica_card_select_t)); - if (arg0) { + felica_card_select_t card = {0}; + uint8_t select_result = felica_select_card(&card); + + int select_status = PM3_SUCCESS; + switch (select_result) { + case 1: + select_status = PM3_ETIMEOUT; + break; + case 2: + select_status = PM3_EWRONGANSWER; + break; + case 3: + select_status = PM3_ECRC; + break; + default: + break; + } + + reply_ng(CMD_HF_FELICA_COMMAND, select_status, (uint8_t *)&card, sizeof(felica_card_select_t)); + if (select_status != PM3_SUCCESS) { felica_reset_frame_mode(); return; } } if ((param & FELICA_RAW) == FELICA_RAW) { - if (len > FELICA_MAX_FRAME_SIZE) { - Dbprintf("FeliCa raw payload too long: %u (max %u)", len, FELICA_MAX_FRAME_SIZE); - reply_mix(CMD_ACK, 0, PM3_ELENGTH, 0, NULL, 0); + if (len > FELICA_MAX_DATA_SIZE) { + Dbprintf("FeliCa raw payload too long: %u (max %u)", len, FELICA_MAX_DATA_SIZE); + reply_ng(CMD_HF_FELICA_COMMAND, PM3_ELENGTH, NULL, 0); if (!no_disconnect) { felica_reset_frame_mode(); } return; } - uint8_t buf[FELICA_MAX_FRAME_SIZE]; + uint8_t buf[FELICA_MAX_RF_FRAME_SIZE]; memset(buf, 0, sizeof(buf)); // add sync bits @@ -590,7 +627,7 @@ void felica_sendraw(const PacketCommandNG *c) { buf[2] = len + 1; // copy command - memcpy(buf + 3, c->data.asBytes, len); + memcpy(buf + 3, payload, len); if ((param & FELICA_APPEND_CRC) == FELICA_APPEND_CRC) { // Don't append crc on empty bytearray... @@ -609,17 +646,16 @@ void felica_sendraw(const PacketCommandNG *c) { }; TransmitFor18092_AsReader(buf, buf[2] + 4, NULL, 1, 0); - arg0 = WaitForFelicaReply(1024); + bool got_frame = WaitForFelicaReply(1024); if (g_dbglevel >= DBG_DEBUG) { - Dbprintf("Received Frame Code: %d", arg0); + Dbprintf("Received Frame Code: %d", got_frame); Dbhexdump(FelicaFrame.len, FelicaFrame.framebytes, 0); }; - uint32_t result = reply_mix(CMD_ACK, FelicaFrame.len, arg0, 0, FelicaFrame.framebytes, FelicaFrame.len); - if (result) { - Dbprintf("Reply to Client Error Code: %i", result); - } + int status = got_frame ? PM3_SUCCESS : PM3_ERFTRANS; + uint16_t frame_len = got_frame ? FelicaFrame.len : 0; + reply_ng(CMD_HF_FELICA_COMMAND, status, got_frame ? FelicaFrame.framebytes : NULL, frame_len); } if (no_disconnect) { @@ -937,5 +973,10 @@ void felica_dump_lite_s(void) { // setting tracelen - important! it was set by buffer overflow before // iceman: is this still needed?!? set_tracelen(cnt); - reply_mix(CMD_ACK, isOK, cnt, 0, 0, 0); + + felica_lite_dump_resp_t payload = { + .completed = isOK, + .tracelen = cnt, + }; + reply_ng(CMD_HF_FELICALITE_DUMP, isOK ? PM3_SUCCESS : PM3_EOPABORTED, (uint8_t *)&payload, sizeof(payload)); } diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 08ddc5df4..1825d8aeb 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -52,6 +52,7 @@ static int CmdHelp(const char *Cmd); +static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose); static felica_card_select_t last_known_card; static void set_last_known_card(felica_card_select_t card) { @@ -281,12 +282,19 @@ static const char *felica_model_name(uint8_t rom_type, uint8_t ic_type) { * @param verbose prints out the response received. */ static bool waitCmdFelica(bool iSelect, PacketResponseNG *resp, bool verbose) { - if (WaitForResponseTimeout(CMD_ACK, resp, 2000) == false) { + if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, resp, 2000) == false) { PrintAndLogEx(WARNING, "timeout while waiting for reply"); return false; } - uint16_t len = (iSelect) ? (resp->oldarg[1] & 0xffff) : (resp->oldarg[0] & 0xffff); + if (resp->status != PM3_SUCCESS) { + if (verbose) { + PrintAndLogEx(WARNING, "FeliCa command failed (%d)", resp->status); + } + return false; + } + + uint16_t len = resp->length; if (verbose) { @@ -298,11 +306,15 @@ static bool waitCmdFelica(bool iSelect, PacketResponseNG *resp, bool verbose) { PrintAndLogEx(SUCCESS, "(%u) %s", len, sprint_hex(resp->data.asBytes, len)); if (iSelect == false) { + if (len < 4) { + PrintAndLogEx(ERR, "received too short frame!"); + return false; + } if (check_crc(CRC_FELICA, resp->data.asBytes + 2, len - 2) == false) { PrintAndLogEx(WARNING, "CRC ( " _RED_("fail") " )"); } - if (resp->data.asBytes[0] != 0xB2 && resp->data.asBytes[1] != 0x4D) { + if (resp->data.asBytes[0] != 0xB2 || resp->data.asBytes[1] != 0x4D) { PrintAndLogEx(ERR, "received incorrect frame format!"); return false; } @@ -335,26 +347,35 @@ int read_felica_uid(bool loop, bool verbose) { int res = PM3_ETIMEOUT; do { - clearCommandBuffer(); - SendCommandMIX(CMD_HF_FELICA_COMMAND, FELICA_CONNECT, 0, 0, NULL, 0); + clear_and_send_command(FELICA_CONNECT, 0, NULL, false); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 2500)) { + if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, &resp, 2500)) { - uint8_t status = resp.oldarg[0] & 0xFF; + int status = resp.status; if (loop) { - if (status != 0) { + if (status != PM3_SUCCESS) { continue; } } else { // when not in continuous mode - if (status != 0) { - if (verbose) PrintAndLogEx(WARNING, "FeliCa card select failed"); - res = PM3_EOPABORTED; + if (status != PM3_SUCCESS) { + if (verbose) { + PrintAndLogEx(WARNING, "FeliCa card select failed (%d)", status); + } + res = status; break; } } + if (resp.length < sizeof(felica_card_select_t)) { + if (verbose) { + PrintAndLogEx(WARNING, "FeliCa card select returned invalid payload"); + } + res = PM3_ESOFT; + break; + } + felica_card_select_t card; memcpy(&card, (felica_card_select_t *)resp.data.asBytes, sizeof(felica_card_select_t)); if (loop == false) { @@ -399,50 +420,60 @@ static int CmdHFFelicaReader(const char *Cmd) { static int info_felica(bool verbose) { - clearCommandBuffer(); - SendCommandMIX(CMD_HF_FELICA_COMMAND, FELICA_CONNECT, 0, 0, NULL, 0); + clear_and_send_command(FELICA_CONNECT, 0, NULL, false); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, &resp, 2500) == false) { if (verbose) PrintAndLogEx(WARNING, "FeliCa card select failed"); return PM3_ESOFT; } + if (resp.status != PM3_SUCCESS) { + switch (resp.status) { + case PM3_ETIMEOUT: + if (verbose) { + PrintAndLogEx(WARNING, "card timeout"); + } + break; + case PM3_EWRONGANSWER: + if (verbose) { + PrintAndLogEx(WARNING, "card answered wrong"); + } + break; + case PM3_ECRC: + if (verbose) { + PrintAndLogEx(WARNING, "CRC check failed"); + } + break; + default: + if (verbose) { + PrintAndLogEx(WARNING, "FeliCa card select failed (%d)", resp.status); + } + break; + } + return resp.status; + } + + if (resp.length < sizeof(felica_card_select_t)) { + if (verbose) { + PrintAndLogEx(WARNING, "FeliCa card select returned invalid payload"); + } + return PM3_ESOFT; + } + felica_card_select_t card; memcpy(&card, (felica_card_select_t *)resp.data.asBytes, sizeof(felica_card_select_t)); - uint64_t status = resp.oldarg[0]; - - switch (status) { - case 1: { - if (verbose) - PrintAndLogEx(WARNING, "card timeout"); - return PM3_ETIMEOUT; - } - case 2: { - if (verbose) - PrintAndLogEx(WARNING, "card answered wrong"); - return PM3_ESOFT; - } - case 3: { - if (verbose) - PrintAndLogEx(WARNING, "CRC check failed"); - return PM3_ESOFT; - } - case 0: { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); - PrintAndLogEx(INFO, "IDm............ " _GREEN_("%s"), sprint_hex_inrow(card.IDm, sizeof(card.IDm))); - PrintAndLogEx(INFO, "Code........... %s ", sprint_hex_inrow(card.code, sizeof(card.code))); - PrintAndLogEx(INFO, "NFCID2......... %s", sprint_hex_inrow(card.uid, sizeof(card.uid))); - PrintAndLogEx(INFO, "Parameter"); - PrintAndLogEx(INFO, "PAD............ " _YELLOW_("%s"), sprint_hex_inrow(card.PMm, sizeof(card.PMm))); - PrintAndLogEx(INFO, "IC code........ %s ( " _YELLOW_("%s") " )", sprint_hex_inrow(card.iccode, sizeof(card.iccode)), felica_model_name(card.iccode[0], card.iccode[1])); - PrintAndLogEx(INFO, "MRT............ %s", sprint_hex_inrow(card.mrt, sizeof(card.mrt))); - PrintAndLogEx(INFO, "Service code... " _YELLOW_("%s"), sprint_hex(card.servicecode, sizeof(card.servicecode))); - PrintAndLogEx(NORMAL, ""); - set_last_known_card(card); - break; - } - } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(INFO, "IDm............ " _GREEN_("%s"), sprint_hex_inrow(card.IDm, sizeof(card.IDm))); + PrintAndLogEx(INFO, "Code........... %s ", sprint_hex_inrow(card.code, sizeof(card.code))); + PrintAndLogEx(INFO, "NFCID2......... %s", sprint_hex_inrow(card.uid, sizeof(card.uid))); + PrintAndLogEx(INFO, "Parameter"); + PrintAndLogEx(INFO, "PAD............ " _YELLOW_("%s"), sprint_hex_inrow(card.PMm, sizeof(card.PMm))); + PrintAndLogEx(INFO, "IC code........ %s ( " _YELLOW_("%s") " )", sprint_hex_inrow(card.iccode, sizeof(card.iccode)), felica_model_name(card.iccode[0], card.iccode[1])); + PrintAndLogEx(INFO, "MRT............ %s", sprint_hex_inrow(card.mrt, sizeof(card.mrt))); + PrintAndLogEx(INFO, "Service code... " _YELLOW_("%s"), sprint_hex(card.servicecode, sizeof(card.servicecode))); + PrintAndLogEx(NORMAL, ""); + set_last_known_card(card); return PM3_SUCCESS; } @@ -462,10 +493,9 @@ static int CmdHFFelicaInfo(const char *Cmd) { } /** - * Clears command buffer and sends the given data to pm3 with mix mode. + * Clears command buffer and sends the given data to pm3 with NG mode. */ -static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose) { - uint16_t numbits = 0; +static void clear_and_send_command_ex(uint8_t flags, uint16_t datalen, uint8_t *data, uint16_t numbits, bool verbose, bool normalize_frame) { uint16_t payload_len = 0; uint8_t *payload = data; @@ -473,7 +503,7 @@ static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *dat // A bunch of code in this module adds length byte at data[0] regardless of that, which is wrong // This is a workaround to extract the actual payload correctly so that length byte isn't repeated // It also strips CRC if present, as ARMSRC adds it too - if (data && datalen) { + if (normalize_frame && data && datalen) { if (datalen >= data[0] && data[0] > 0) { payload_len = data[0] - 1; if (payload_len > datalen - 1) { @@ -483,13 +513,29 @@ static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *dat } else { payload_len = datalen; } + } else { + payload_len = datalen; } clearCommandBuffer(); if (verbose) { PrintAndLogEx(INFO, "Send raw command - Frame: %s", sprint_hex(payload, payload_len)); } - SendCommandMIX(CMD_HF_FELICA_COMMAND, flags, (payload_len & 0xFFFF) | (uint32_t)(numbits << 16), 0, payload, payload_len); + + uint8_t packet_buf[sizeof(felica_raw_cmd_t) + PM3_CMD_DATA_SIZE] = {0}; + felica_raw_cmd_t *packet = (felica_raw_cmd_t *)packet_buf; + packet->flags = flags; + packet->numbits = numbits; + packet->rawlen = payload_len; + if (payload_len) { + memcpy(packet->raw, payload, payload_len); + } + + SendCommandNG(CMD_HF_FELICA_COMMAND, packet_buf, FELICA_RAW_LEN(payload_len)); +} + +static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose) { + clear_and_send_command_ex(flags, datalen, data, 0, verbose, true); } /** @@ -3015,7 +3061,7 @@ static int CmdHFFelicaDumpLite(const char *Cmd) { PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to abort dumping"); uint8_t timeout = 0; - while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) { + while (WaitForResponseTimeout(CMD_HF_FELICALITE_DUMP, &resp, 2000) == false) { if (kbd_enter_pressed()) { SendCommandNG(CMD_BREAK_LOOP, NULL, 0); @@ -3041,12 +3087,28 @@ static int CmdHFFelicaDumpLite(const char *Cmd) { PrintAndLogEx(NORMAL, ""); - if (resp.oldarg[0] == 0) { + if (resp.status == PM3_EOPABORTED) { PrintAndLogEx(WARNING, "Button pressed, aborted"); return PM3_EOPABORTED; } - uint16_t tracelen = resp.oldarg[1]; + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "FeliCa lite dump failed (%d)", resp.status); + return resp.status; + } + + if (resp.length < sizeof(felica_lite_dump_resp_t)) { + PrintAndLogEx(WARNING, "Unexpected dump response length"); + return PM3_ESOFT; + } + + felica_lite_dump_resp_t *dump_resp = (felica_lite_dump_resp_t *)resp.data.asBytes; + if (dump_resp->completed == 0) { + PrintAndLogEx(WARNING, "Button pressed, aborted"); + return PM3_EOPABORTED; + } + + uint16_t tracelen = dump_resp->tracelen; if (tracelen == 0) { PrintAndLogEx(WARNING, "No trace data! Maybe not a FeliCa Lite card?"); return PM3_ESOFT; @@ -3065,7 +3127,7 @@ static int CmdHFFelicaDumpLite(const char *Cmd) { } - PrintAndLogEx(SUCCESS, "Recorded Activity (trace len = %"PRIu32" bytes)", tracelen); + PrintAndLogEx(SUCCESS, "Recorded Activity (trace len = %"PRIu32" bytes)", (uint32_t)tracelen); print_hex_break(trace, tracelen, 32); printSep(); @@ -3145,10 +3207,9 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) { // Max transport buffer is PM3_CMD_DATA_SIZE datalen = (datalen > PM3_CMD_DATA_SIZE) ? PM3_CMD_DATA_SIZE : datalen; - clearCommandBuffer(); PrintAndLogEx(SUCCESS, "Data: %s", sprint_hex(data, datalen)); - SendCommandMIX(CMD_HF_FELICA_COMMAND, flags, (datalen & 0xFFFF) | (uint32_t)(numbits << 16), 0, data, datalen); + clear_and_send_command_ex(flags, datalen, data, numbits, false, false); if (reply) { diff --git a/include/iso18.h b/include/iso18.h index efd85df06..60fa431d9 100644 --- a/include/iso18.h +++ b/include/iso18.h @@ -28,6 +28,20 @@ typedef enum FELICA_COMMAND { FELICA_NO_SELECT = (1 << 6), } felica_command_t; +typedef struct { + uint8_t flags; // PM3 flags, see felica_command_t + uint16_t numbits; // optional number of bits for raw exchange + uint16_t rawlen; // bytes in raw[] + uint8_t raw[]; +} PACKED felica_raw_cmd_t; + +#define FELICA_RAW_LEN(x) (sizeof(felica_raw_cmd_t) + (x)) + +typedef struct { + uint8_t completed; + uint16_t tracelen; +} PACKED felica_lite_dump_resp_t; + //----------------------------------------------------------------------------- // FeliCa //-----------------------------------------------------------------------------