From 1d0faaadb4d145cd566cfdb084e38ce57d060533 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:42:03 +0300 Subject: [PATCH] Add 'hf felica seacauth1' command --- CHANGELOG.md | 1 + client/src/cmdhffelica.c | 186 +++++++++++++++++++++++++------- client/src/pm3line_vocabulary.h | 1 + 3 files changed, 148 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a3580e53..d2f28c41e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `hf felica seacauth1` command - Added `lf trovan` commands to support Trovan Animal ID (@iceman1001) - Added `hf mf gdmgetblk/gdmgethidblk/gdmsethidblk/gdmsetuid/gdmwipe/gdmsetsig` (@0x6r1an0y) - Changed `hf mf gdmparsecfg/gdmsetblk` (@0x6r1an0y) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index cabfc145a..99a0843e0 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -69,7 +69,10 @@ #define FELICA_SEAC_POLL_TIME_SLOT 0x01U #define FELICA_SEAC_IDM_LEN 8U #define FELICA_SEAC_CTX_LEN 2U +#define FELICA_SEAC_CHALLENGE_LEN 8U +#define FELICA_SEAC_AUTH1_FRAME_LEN 21U #define FELICA_SEAC_POLL_RESPONSE_DATA_LEN (FELICA_SEAC_IDM_LEN + FELICA_SEAC_CTX_LEN) +#define FELICA_SEAC_AUTH1_RESPONSE_DATA_LEN ((2U * FELICA_SEAC_CHALLENGE_LEN) + 1U) #define FELICA_REQUEST_SYSTEM_CODE_ATTEMPTS 5U #define FELICA_SYSTEM_CODE_MAX_COUNT 16U #define FELICA_DISCOVERED_SYSTEM_MAX_COUNT FELICA_SYSTEM_CODE_MAX_COUNT @@ -1930,10 +1933,8 @@ static bool waitCmdFelica(bool iSelect, PacketResponseNG *resp, bool verbose) { return waitCmdFelicaEx(iSelect, resp, verbose, true, FELICA_DEFAULT_TIMEOUT_MS); } -// Poll response: 00 01 01 Selector IDm[8] CTX[2] -static bool get_seac_poll_response_data(const PacketResponseNG *resp, uint8_t selector, - const uint8_t **response_data, - size_t *response_data_len) { +static bool get_seac_response_data(const PacketResponseNG *resp, uint8_t command, uint8_t selector, + const uint8_t **response_data, size_t *response_data_len) { if (resp == NULL || response_data == NULL || response_data_len == NULL) { return false; } @@ -1964,8 +1965,8 @@ static bool get_seac_poll_response_data(const PacketResponseNG *resp, uint8_t se return false; } - // The response echoes CMD, System Code, and selector, but not the request's Time Slot. - const uint8_t expected_header[] = {FELICA_SEAC_POLLING_CMD, 0x01, 0x01, selector}; + // The response echoes CMD, System Code, and selector. + const uint8_t expected_header[] = {command, 0x01, 0x01, selector}; if (memcmp(resp->data.asBytes + 3, expected_header, sizeof(expected_header)) != 0) { return false; } @@ -1981,45 +1982,82 @@ static bool get_seac_poll_response_data(const PacketResponseNG *resp, uint8_t se return true; } -int info_felica_seac(void) { - static const uint8_t seac_poll_frames[][FELICA_SEAC_POLL_FRAME_LEN] = { - // LEN CMD System Code selector Time Slot - {0x06, FELICA_SEAC_POLLING_CMD, 0x01, 0x01, 0x01, FELICA_SEAC_POLL_TIME_SLOT}, +static int poll_seac(uint8_t selector, uint8_t *idm, uint8_t *ctx) { + uint8_t poll_frame[FELICA_SEAC_POLL_FRAME_LEN] = { + FELICA_SEAC_POLL_FRAME_LEN, FELICA_SEAC_POLLING_CMD, 0x01, 0x01, selector, FELICA_SEAC_POLL_TIME_SLOT, }; const uint8_t seac_flags = FELICA_CONNECT | FELICA_CLEARTRACE | FELICA_RAW | FELICA_APPEND_CRC | FELICA_NO_SELECT; - for (size_t i = 0; i < ARRAYLEN(seac_poll_frames); i++) { - PacketResponseNG resp; - if (send_felica_payload_with_retries(seac_flags, sizeof(seac_poll_frames[i]), - (uint8_t *)seac_poll_frames[i], false, - -1, FELICA_SEAC_POLL_TIMEOUT_MS, FELICA_SEAC_POLL_RETRY_COUNT, - 0, false, &resp, NULL) != PM3_SUCCESS) { - continue; - } - - const uint8_t *response_data = NULL; - size_t response_data_len = 0; - if (get_seac_poll_response_data(&resp, seac_poll_frames[i][4], - &response_data, &response_data_len) == false || - response_data_len != FELICA_SEAC_POLL_RESPONSE_DATA_LEN) { - continue; - } - - const uint8_t *idm = response_data; - const uint8_t *ctx = response_data + FELICA_SEAC_IDM_LEN; - - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); - PrintAndLogEx(INFO, "Type........... " _YELLOW_("FeliCa SEAC")); - PrintAndLogEx(INFO, "IDm............ " _YELLOW_("%s"), - sprint_hex_inrow(idm, FELICA_SEAC_IDM_LEN)); - PrintAndLogEx(INFO, "CTX............ " _YELLOW_("%s"), - sprint_hex_inrow(ctx, FELICA_SEAC_CTX_LEN)); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; + PacketResponseNG resp; + int ret = send_felica_payload_with_retries(seac_flags, sizeof(poll_frame), poll_frame, false, + -1, FELICA_SEAC_POLL_TIMEOUT_MS, FELICA_SEAC_POLL_RETRY_COUNT, + 0, false, &resp, NULL); + if (ret != PM3_SUCCESS) { + return ret; } - return PM3_ETIMEOUT; + const uint8_t *response_data = NULL; + size_t response_data_len = 0; + if (get_seac_response_data(&resp, FELICA_SEAC_POLLING_CMD, selector, + &response_data, &response_data_len) == false || + response_data_len != FELICA_SEAC_POLL_RESPONSE_DATA_LEN) { + return PM3_ESOFT; + } + + memcpy(idm, response_data, FELICA_SEAC_IDM_LEN); + memcpy(ctx, response_data + FELICA_SEAC_IDM_LEN, FELICA_SEAC_CTX_LEN); + return PM3_SUCCESS; +} + +static int auth1_seac(uint8_t selector, const uint8_t *idm, const uint8_t *challenge1a, + uint8_t *challenge1b, uint8_t *challenge2a, uint8_t *trailer) { + uint8_t auth1_frame[FELICA_SEAC_AUTH1_FRAME_LEN] = {0}; + auth1_frame[0] = (uint8_t)sizeof(auth1_frame); + auth1_frame[1] = FELICA_SEAC_AUTHENTICATION1_CMD; + auth1_frame[2] = 0x01; + auth1_frame[3] = 0x01; + auth1_frame[4] = selector; + memcpy(auth1_frame + 5, idm, FELICA_SEAC_IDM_LEN); + memcpy(auth1_frame + 5 + FELICA_SEAC_IDM_LEN, challenge1a, FELICA_SEAC_CHALLENGE_LEN); + + const uint8_t seac_flags = FELICA_CONNECT | FELICA_CLEARTRACE | FELICA_RAW | FELICA_APPEND_CRC | FELICA_NO_SELECT; + PacketResponseNG resp; + int ret = send_felica_payload_with_retries(seac_flags, sizeof(auth1_frame), auth1_frame, false, + -1, FELICA_SEAC_POLL_TIMEOUT_MS, FELICA_SEAC_POLL_RETRY_COUNT, + 0, false, &resp, NULL); + if (ret != PM3_SUCCESS) { + return ret; + } + + const uint8_t *response_data = NULL; + size_t response_data_len = 0; + if (get_seac_response_data(&resp, FELICA_SEAC_AUTHENTICATION1_CMD, selector, + &response_data, &response_data_len) == false || + response_data_len != FELICA_SEAC_AUTH1_RESPONSE_DATA_LEN) { + return PM3_ESOFT; + } + + memcpy(challenge1b, response_data, FELICA_SEAC_CHALLENGE_LEN); + memcpy(challenge2a, response_data + FELICA_SEAC_CHALLENGE_LEN, FELICA_SEAC_CHALLENGE_LEN); + *trailer = response_data[FELICA_SEAC_AUTH1_RESPONSE_DATA_LEN - 1U]; + return PM3_SUCCESS; +} + +int info_felica_seac(void) { + uint8_t idm[FELICA_SEAC_IDM_LEN] = {0}; + uint8_t ctx[FELICA_SEAC_CTX_LEN] = {0}; + int ret = poll_seac(0x01, idm, ctx); + if (ret != PM3_SUCCESS) { + return ret; + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(INFO, "Type........... " _YELLOW_("FeliCa SEAC")); + PrintAndLogEx(INFO, "IDm............ " _YELLOW_("%s"), sprint_hex_inrow(idm, sizeof(idm))); + PrintAndLogEx(INFO, "CTX............ " _YELLOW_("%s"), sprint_hex_inrow(ctx, sizeof(ctx))); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; } @@ -2509,6 +2547,72 @@ static int CmdHFFelicaInfo(const char *Cmd) { return ret == PM3_SUCCESS ? ret : info_felica_seac(); } +static int CmdHFFelicaSeacAuth1(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf felica seacauth1", + "Send FeliCa SEAC Authentication1", + "hf felica seacauth1\n" + "hf felica seacauth1 --selector 0F\n" + "hf felica seacauth1 --challenge 0001020304050607\n" + "hf felica seacauth1 -s 0F -c FFFFFFFFFFFFFFFF"); + + void *argtable[] = { + arg_param_begin, + arg_str0("s", "selector", "", "selector, 1 byte (default 01)"), + arg_str0("c", "challenge", "", "Challenge1a, 8 bytes (default all zero)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + uint8_t selector_data[1] = {0}; + int selector_len = 0; + int ret = CLIParamHexToBuf(arg_get_str(ctx, 1), selector_data, sizeof(selector_data), &selector_len); + if (ret != PM3_SUCCESS || (selector_len != 0 && selector_len != (int)sizeof(selector_data))) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "Selector must be exactly 1 byte"); + return PM3_EINVARG; + } + + uint8_t challenge1a[FELICA_SEAC_CHALLENGE_LEN] = {0}; + int challenge_len = 0; + ret = CLIParamHexToBuf(arg_get_str(ctx, 2), challenge1a, sizeof(challenge1a), &challenge_len); + CLIParserFree(ctx); + if (ret != PM3_SUCCESS || (challenge_len != 0 && challenge_len != (int)sizeof(challenge1a))) { + PrintAndLogEx(ERR, "Challenge1a must be exactly 8 bytes"); + return PM3_EINVARG; + } + + const uint8_t selector = selector_len == 0 ? 0x01 : selector_data[0]; + uint8_t idm[FELICA_SEAC_IDM_LEN] = {0}; + uint8_t seac_ctx[FELICA_SEAC_CTX_LEN] = {0}; + ret = poll_seac(selector, idm, seac_ctx); + if (ret != PM3_SUCCESS) { + PrintAndLogEx(ERR, "No SEAC card response for selector %02X", selector); + return ret; + } + + uint8_t challenge1b[FELICA_SEAC_CHALLENGE_LEN] = {0}; + uint8_t challenge2a[FELICA_SEAC_CHALLENGE_LEN] = {0}; + uint8_t trailer = 0; + ret = auth1_seac(selector, idm, challenge1a, challenge1b, challenge2a, &trailer); + if (ret != PM3_SUCCESS) { + PrintAndLogEx(ERR, "No valid SEAC Authentication1 response"); + return ret; + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("SEAC Authentication1") " ----------------------"); + PrintAndLogEx(INFO, "Selector....... " _YELLOW_("%02X"), selector); + PrintAndLogEx(INFO, "IDm............ " _YELLOW_("%s"), sprint_hex_inrow(idm, sizeof(idm))); + PrintAndLogEx(INFO, "CTX............ " _YELLOW_("%s"), sprint_hex_inrow(seac_ctx, sizeof(seac_ctx))); + PrintAndLogEx(INFO, "Challenge1a.... " _YELLOW_("%s"), sprint_hex_inrow(challenge1a, sizeof(challenge1a))); + PrintAndLogEx(INFO, "Challenge1b.... " _YELLOW_("%s"), sprint_hex_inrow(challenge1b, sizeof(challenge1b))); + PrintAndLogEx(INFO, "Challenge2a.... " _YELLOW_("%s"), sprint_hex_inrow(challenge2a, sizeof(challenge2a))); + PrintAndLogEx(INFO, "Trailer........ " _YELLOW_("%02X"), trailer); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + /** * Clears command buffer and sends the given data to pm3 with NG mode. */ @@ -7926,6 +8030,8 @@ static command_t CommandTable[] = { {"reader", CmdHFFelicaReader, IfPm3Felica, "Act like an ISO18092/FeliCa reader"}, {"sniff", CmdHFFelicaSniff, IfPm3Felica, "Sniff ISO 18092/FeliCa traffic"}, {"wrbl", CmdHFFelicaWritePlain, IfPm3Felica, "write block data to an authentication-not-required Service."}, + {"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("FeliCa SEAC") " -----------------------"}, + {"seacauth1", CmdHFFelicaSeacAuth1, IfPm3Felica, "FeliCa SEAC Authentication1"}, {"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("FeliCa Standard") " -----------------------"}, {"dump", CmdHFFelicaDump, IfPm3Felica, "Wait for and try dumping FeliCa"}, {"discnodes", CmdHFFelicaDiscoverNodes, IfPm3Felica, "discover Area Code and Service Code nodes."}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 842d4d46a..7638788e7 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -257,6 +257,7 @@ const static vocabulary_t vocabulary[] = { { 1, "hf felica help" }, { 1, "hf felica list" }, { 0, "hf felica info" }, + { 0, "hf felica seacauth1" }, { 0, "hf felica raw" }, { 0, "hf felica rdbl" }, { 0, "hf felica reader" },