Extract more information with 'hf felica info'

This commit is contained in:
kormax
2026-03-05 17:11:30 +02:00
parent 20eb4294f1
commit ec25ea77a4
5 changed files with 378 additions and 30 deletions
+285 -30
View File
@@ -28,6 +28,7 @@
#include "util.h"
#include "ui.h"
#include "iso18.h" // felica_card_select_t struct
#include "protocols.h"
#include "des.h"
#include "platform_util.h"
#include "cliparser.h" // cliparser
@@ -45,6 +46,10 @@
#define FELICA_DEFAULT_TIMEOUT_MS 2000U
#define FELICA_DEFAULT_RETRY_COUNT 3U
#define FELICA_PLATFORM_INFO_MAX_LEN 64U
#define FELICA_CONTAINER_PROPERTY_MAX_LEN 64U
#define FELICA_OPTIONAL_CMD_TIMEOUT_MS 250U
#define FELICA_OPTIONAL_CMD_RETRIES 3U
#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001)
#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010)
@@ -56,6 +61,9 @@
static int CmdHelp(const char *Cmd);
static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose);
static int send_felica_payload_with_retries(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
int expected_response_cmd, uint32_t timeout_ms, uint32_t retries, bool logging,
PacketResponseNG *resp, const char *request_name);
static felica_card_select_t last_known_card;
static void set_last_known_card(felica_card_select_t card) {
@@ -283,15 +291,18 @@ static const char *felica_model_name(uint8_t rom_type, uint8_t ic_type) {
* Wait for response from pm3 or timeout.
* Checks if receveid bytes have a valid CRC.
* @param verbose prints out the response received.
* @param logging prints warning/error logs.
*/
static bool waitCmdFelicaEx(bool iSelect, PacketResponseNG *resp, bool verbose, uint32_t timeout_ms) {
static bool waitCmdFelicaEx(bool iSelect, PacketResponseNG *resp, bool verbose, bool logging, uint32_t timeout_ms) {
if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, resp, timeout_ms) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
if (logging) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
}
return false;
}
if (resp->status != PM3_SUCCESS) {
if (verbose) {
if (logging) {
PrintAndLogEx(WARNING, "FeliCa command failed (%d)", resp->status);
}
return false;
@@ -299,35 +310,43 @@ static bool waitCmdFelicaEx(bool iSelect, PacketResponseNG *resp, bool verbose,
uint16_t len = resp->length;
if (verbose) {
if (len == 0 || len == 1) {
if (len == 0 || len == 1) {
if (logging) {
PrintAndLogEx(ERR, "Could not receive data correctly!");
}
return false;
}
if (iSelect == false) {
if (len < 4) {
if (logging) {
PrintAndLogEx(ERR, "received too short frame!");
}
return false;
}
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) {
if (check_crc(CRC_FELICA, resp->data.asBytes + 2, len - 2) == false) {
if (logging) {
PrintAndLogEx(WARNING, "CRC ( " _RED_("fail") " )");
}
if (resp->data.asBytes[0] != 0xB2 || resp->data.asBytes[1] != 0x4D) {
PrintAndLogEx(ERR, "received incorrect frame format!");
return false;
}
}
if (resp->data.asBytes[0] != 0xB2 || resp->data.asBytes[1] != 0x4D) {
if (logging) {
PrintAndLogEx(ERR, "received incorrect frame format!");
}
return false;
}
}
if (verbose && logging) {
PrintAndLogEx(SUCCESS, "(%u) %s", len, sprint_hex(resp->data.asBytes, len));
}
return true;
}
static bool waitCmdFelica(bool iSelect, PacketResponseNG *resp, bool verbose) {
return waitCmdFelicaEx(iSelect, resp, verbose, FELICA_DEFAULT_TIMEOUT_MS);
return waitCmdFelicaEx(iSelect, resp, verbose, true, FELICA_DEFAULT_TIMEOUT_MS);
}
@@ -425,11 +444,150 @@ static int CmdHFFelicaReader(const char *Cmd) {
return read_felica_uid(cm, verbose);
}
static int send_get_container_id(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
felica_get_container_id_response_t *container_id_response) {
(void)verbose;
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, false,
FELICA_GET_CONTAINER_ID_ACK,
FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES,
false, &resp, "get container id") != PM3_SUCCESS) {
return PM3_ERFTRANS;
}
if (resp.length < sizeof(*container_id_response)) {
return PM3_ESOFT;
}
memcpy(container_id_response, (felica_get_container_id_response_t *)resp.data.asBytes, sizeof(*container_id_response));
return PM3_SUCCESS;
}
static int send_get_container_property(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
uint8_t *container_property_data, size_t container_property_data_capacity,
size_t *container_property_data_len) {
(void)verbose;
if (container_property_data == NULL || container_property_data_len == NULL) {
return PM3_EINVARG;
}
*container_property_data_len = 0;
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, false,
FELICA_GET_CONTAINER_PROPERTY_ACK,
FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES,
false, &resp, "get container property") != PM3_SUCCESS) {
return PM3_ERFTRANS;
}
if (resp.length < sizeof(felica_get_container_property_response_t)) {
return PM3_ESOFT;
}
const size_t frame_len_offset = 2;
if (resp.data.asBytes[frame_len_offset] < 2) {
return PM3_ESOFT;
}
const size_t property_data_len = resp.data.asBytes[frame_len_offset] - 2;
if (property_data_len == 0 || property_data_len > FELICA_CONTAINER_PROPERTY_MAX_LEN) {
return PM3_ESOFT;
}
const size_t property_data_offset = sizeof(felica_frame_response_noidm_t);
if (resp.length < property_data_offset + property_data_len) {
return PM3_ESOFT;
}
size_t copy_len = property_data_len;
if (copy_len > container_property_data_capacity) {
copy_len = container_property_data_capacity;
}
memcpy(container_property_data, resp.data.asBytes + property_data_offset, copy_len);
*container_property_data_len = copy_len;
return PM3_SUCCESS;
}
static int send_get_container_issue_information(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
felica_get_container_issue_info_response_t *container_issue_info_response) {
(void)verbose;
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, false,
FELICA_GET_CONTAINER_ISSUE_INFO_ACK,
FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES,
false, &resp, "get container issue info") != PM3_SUCCESS) {
return PM3_ERFTRANS;
}
if (resp.length < sizeof(*container_issue_info_response)) {
return PM3_ESOFT;
}
memcpy(container_issue_info_response, (felica_get_container_issue_info_response_t *)resp.data.asBytes, sizeof(*container_issue_info_response));
return PM3_SUCCESS;
}
static int send_get_platform_information(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
felica_status_flags_t *status_flags, uint8_t *platform_information_data,
size_t platform_information_data_capacity, size_t *platform_information_data_len) {
(void)verbose;
if (status_flags == NULL || platform_information_data == NULL || platform_information_data_len == NULL) {
return PM3_EINVARG;
}
*platform_information_data_len = 0;
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, false,
FELICA_GETPLATFORMINFO_ACK,
FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES,
false, &resp, "get platform info") != PM3_SUCCESS) {
return PM3_ERFTRANS;
}
if (resp.length < (sizeof(felica_frame_response_t) + sizeof(felica_status_flags_t))) {
return PM3_ESOFT;
}
const size_t status_offset = sizeof(felica_frame_response_t);
memcpy(status_flags, resp.data.asBytes + status_offset, sizeof(*status_flags));
if (status_flags->status_flag1[0] != 0x00 || status_flags->status_flag2[0] != 0x00) {
return PM3_SUCCESS;
}
const size_t data_len_offset = sizeof(felica_frame_response_t) + sizeof(felica_status_flags_t);
if (resp.length < (data_len_offset + 1)) {
return PM3_ESOFT;
}
const size_t payload_len = resp.length - (data_len_offset + 1);
const size_t data_len = resp.data.asBytes[data_len_offset];
if (data_len > FELICA_PLATFORM_INFO_MAX_LEN) {
return PM3_ESOFT;
}
if (payload_len < data_len) {
return PM3_ESOFT;
}
size_t copy_len = data_len;
if (copy_len > platform_information_data_capacity) {
copy_len = platform_information_data_capacity;
}
memcpy(platform_information_data, resp.data.asBytes + data_len_offset + 1, copy_len);
*platform_information_data_len = copy_len;
return PM3_SUCCESS;
}
static int info_felica(bool verbose) {
clear_and_send_command(FELICA_CONNECT, 0, NULL, false);
clear_and_send_command(FELICA_CONNECT | FELICA_NO_DISCONNECT, 0, NULL, false);
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, &resp, 2500) == false) {
DropField();
if (verbose) PrintAndLogEx(WARNING, "FeliCa card select failed");
return PM3_ESOFT;
}
@@ -457,6 +615,7 @@ static int info_felica(bool verbose) {
}
break;
}
DropField();
return resp.status;
}
@@ -464,6 +623,7 @@ static int info_felica(bool verbose) {
if (verbose) {
PrintAndLogEx(WARNING, "FeliCa card select returned invalid payload");
}
DropField();
return PM3_ESOFT;
}
@@ -479,8 +639,98 @@ static int info_felica(bool verbose) {
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);
const uint8_t optional_flags = FELICA_NO_DISCONNECT | FELICA_APPEND_CRC | FELICA_RAW;
felica_get_platform_info_request_t platform_info_request;
memset(&platform_info_request, 0, sizeof(platform_info_request));
platform_info_request.length[0] = sizeof(platform_info_request);
platform_info_request.command_code[0] = FELICA_GETPLATFORMINFO_REQ;
memcpy(platform_info_request.IDm, card.IDm, sizeof(platform_info_request.IDm));
felica_status_flags_t platform_status_flags;
uint8_t platform_information_data[FELICA_PLATFORM_INFO_MAX_LEN] = {0};
size_t platform_information_data_len = 0;
if (send_get_platform_information(optional_flags,
sizeof(platform_info_request), (uint8_t *)&platform_info_request,
false, &platform_status_flags, platform_information_data,
sizeof(platform_information_data),
&platform_information_data_len) == PM3_SUCCESS &&
platform_information_data_len > 0) {
PrintAndLogEx(INFO, "Platform info.. %s", sprint_hex_inrow(platform_information_data, platform_information_data_len));
}
felica_get_container_id_request_t container_id_request;
memset(&container_id_request, 0, sizeof(container_id_request));
container_id_request.length[0] = sizeof(container_id_request);
container_id_request.command_code[0] = FELICA_GET_CONTAINER_ID_REQ;
felica_get_container_id_response_t container_id_response;
if (send_get_container_id(optional_flags, sizeof(container_id_request),
(uint8_t *)&container_id_request, false,
&container_id_response) == PM3_SUCCESS) {
PrintAndLogEx(INFO, "Container IDm.. " _YELLOW_("%s"),
sprint_hex_inrow(container_id_response.container_idm, sizeof(container_id_response.container_idm)));
}
felica_get_container_issue_info_request_t container_issue_info_request;
memset(&container_issue_info_request, 0, sizeof(container_issue_info_request));
container_issue_info_request.length[0] = sizeof(container_issue_info_request);
container_issue_info_request.command_code[0] = FELICA_GET_CONTAINER_ISSUE_INFO_REQ;
memcpy(container_issue_info_request.IDm, card.IDm, sizeof(container_issue_info_request.IDm));
felica_get_container_issue_info_response_t container_issue_info_response;
if (send_get_container_issue_information(optional_flags,
sizeof(container_issue_info_request), (uint8_t *)&container_issue_info_request, false,
&container_issue_info_response) == PM3_SUCCESS) {
char model_ascii[sizeof(container_issue_info_response.mobile_phone_model_information) + 1] = {0};
bool model_is_ascii = decode_zero_padded_ascii(
container_issue_info_response.mobile_phone_model_information,
sizeof(container_issue_info_response.mobile_phone_model_information),
model_ascii,
sizeof(model_ascii)
);
PrintAndLogEx(INFO, "Container issue info:");
PrintAndLogEx(INFO, " Format/Carrier... %s",
sprint_hex_inrow(container_issue_info_response.format_version_carrier_information,
sizeof(container_issue_info_response.format_version_carrier_information)));
if (model_is_ascii) {
PrintAndLogEx(INFO, " Model............ %s (ASCII)", model_ascii);
} else {
PrintAndLogEx(INFO, " Model............ %s (HEX)",
sprint_hex_inrow(container_issue_info_response.mobile_phone_model_information,
sizeof(container_issue_info_response.mobile_phone_model_information)));
}
}
const uint16_t container_properties[] = {0x0000, 0x0001};
uint8_t container_property_data[FELICA_CONTAINER_PROPERTY_MAX_LEN] = {0};
bool has_container_properties = false;
for (size_t i = 0; i < (sizeof(container_properties) / sizeof(container_properties[0])); i++) {
felica_get_container_property_request_t container_property_request;
memset(&container_property_request, 0, sizeof(container_property_request));
container_property_request.length[0] = sizeof(container_property_request);
container_property_request.command_code[0] = FELICA_GET_CONTAINER_PROPERTY_REQ;
container_property_request.property_index[0] = container_properties[i] & 0xFF;
container_property_request.property_index[1] = (container_properties[i] >> 8) & 0xFF;
size_t container_property_data_len = 0;
if (send_get_container_property(optional_flags, sizeof(container_property_request),
(uint8_t *)&container_property_request, false,
container_property_data, sizeof(container_property_data),
&container_property_data_len) == PM3_SUCCESS &&
container_property_data_len > 0) {
if (has_container_properties == false) {
PrintAndLogEx(INFO, "Container properties:");
has_container_properties = true;
}
PrintAndLogEx(INFO, " 0x%04X........... %s", container_properties[i],
sprint_hex_inrow(container_property_data, container_property_data_len));
}
}
DropField();
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
@@ -600,29 +850,30 @@ static void log_felica_retry_attempt(const char *request_name, uint32_t attempt,
* @return PM3_SUCCESS on success
*/
static int send_felica_payload_with_retries(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
int expected_response_cmd, uint32_t timeout_ms, uint32_t retries, PacketResponseNG *resp, const char *request_name) {
int expected_response_cmd, uint32_t timeout_ms, uint32_t retries, bool logging,
PacketResponseNG *resp, const char *request_name) {
for (uint32_t attempt = 0; attempt <= retries; attempt++) {
clear_and_send_command(flags, datalen, data, verbose);
if (waitCmdFelicaEx(false, resp, verbose, timeout_ms) == false) {
if (attempt < retries) {
if (waitCmdFelicaEx(false, resp, verbose, logging, timeout_ms) == false) {
if (logging && attempt < retries) {
log_felica_retry_attempt(request_name, attempt, retries);
}
continue;
}
if (expected_response_cmd >= 0) {
if (resp->length < sizeof(felica_frame_response_t)) {
if (attempt < retries) {
if (resp->length < sizeof(felica_frame_response_noidm_t)) {
if (logging && attempt < retries) {
log_felica_retry_attempt(request_name, attempt, retries);
}
continue;
}
const felica_frame_response_t *frame_response = (const felica_frame_response_t *)resp->data.asBytes;
const felica_frame_response_noidm_t *frame_response = (const felica_frame_response_noidm_t *)resp->data.asBytes;
if (frame_response->cmd_code[0] != (uint8_t)expected_response_cmd) {
if (attempt < retries) {
if (logging && attempt < retries) {
log_felica_retry_attempt(request_name, attempt, retries);
} else {
} else if (logging) {
PrintAndLogEx(FAILED, "Bad response cmd 0x%02X (expected 0x%02X).",
frame_response->cmd_code[0], (uint8_t)expected_response_cmd);
}
@@ -647,6 +898,7 @@ int send_request_service(uint8_t flags, uint16_t datalen, uint8_t *data, bool ve
if (send_felica_payload_with_retries(flags, datalen, data, verbose,
0x03,
FELICA_DEFAULT_TIMEOUT_MS, 0,
true,
&resp, "request service") != PM3_SUCCESS) {
PrintAndLogEx(ERR, "\nGot no response from card");
return PM3_ERFTRANS;
@@ -679,6 +931,7 @@ static int send_read_without_encryption_ex(uint8_t flags, uint16_t datalen, uint
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, verbose,
0x07, timeout_ms, retries,
true,
&resp, "read without encryption") != PM3_SUCCESS) {
PrintAndLogEx(ERR, "No response from card");
return PM3_ERFTRANS;
@@ -715,6 +968,7 @@ static int send_search_service_code(uint8_t flags, uint16_t datalen, uint8_t *da
PacketResponseNG resp;
if (send_felica_payload_with_retries(flags, datalen, data, verbose,
0x0B, timeout_ms, retries,
true,
&resp, "search service") != PM3_SUCCESS) {
PrintAndLogEx(ERR, "No response from card");
return PM3_ERFTRANS;
@@ -754,6 +1008,7 @@ static int send_write_without_encryption(uint8_t flags, uint16_t datalen, uint8_
if (send_felica_payload_with_retries(flags, datalen, data, verbose,
-1,
FELICA_DEFAULT_TIMEOUT_MS, 0,
true,
&resp, "write block") != PM3_SUCCESS) {
PrintAndLogEx(ERR, "no response from card");
return PM3_ERFTRANS;
+33
View File
@@ -1368,6 +1368,39 @@ void clean_ascii(unsigned char *buf, size_t len) {
}
}
bool decode_zero_padded_ascii(const uint8_t *data, size_t data_len, char *out, size_t out_len) {
if (data == NULL || out == NULL || out_len == 0) {
return false;
}
size_t text_len = 0;
while (text_len < data_len && data[text_len] != 0x00) {
if (data[text_len] < 0x20 || data[text_len] > 0x7E) {
return false;
}
text_len++;
}
if (text_len == 0 || text_len == data_len) {
return false;
}
for (size_t i = text_len; i < data_len; i++) {
if (data[i] != 0x00) {
return false;
}
}
size_t copy_len = text_len;
if (copy_len > out_len - 1) {
copy_len = out_len - 1;
}
memcpy(out, data, copy_len);
out[copy_len] = '\0';
return true;
}
// replace \r \n to \0
void str_cleanrn(char *buf, size_t len) {
str_creplace(buf, len, '\n', '\0');
+1
View File
@@ -166,6 +166,7 @@ void strn_upper(char *s, size_t n);
bool str_startswith(const char *s, const char *pre); // check for prefix in string
bool str_endswith(const char *s, const char *suffix); // check for suffix in string
void clean_ascii(unsigned char *buf, size_t len);
bool decode_zero_padded_ascii(const uint8_t *data, size_t data_len, char *out, size_t out_len);
void str_cleanrn(char *buf, size_t len);
void str_creplace(char *buf, size_t len, char from, char to);
void str_reverse(char *buf, size_t len);
+47
View File
@@ -81,11 +81,58 @@ typedef struct {
uint8_t IDm[8];
} PACKED felica_frame_response_t;
typedef struct {
uint8_t sync[2];
uint8_t length[1];
uint8_t cmd_code[1];
} PACKED felica_frame_response_noidm_t;
typedef struct {
uint8_t status_flag1[1];
uint8_t status_flag2[1];
} PACKED felica_status_flags_t;
typedef struct {
uint8_t length[1];
uint8_t command_code[1];
uint8_t reserved[2];
} PACKED felica_get_container_id_request_t;
typedef struct {
felica_frame_response_noidm_t frame_response;
uint8_t container_idm[8];
} PACKED felica_get_container_id_response_t;
typedef struct {
uint8_t length[1];
uint8_t command_code[1];
uint8_t property_index[2];
} PACKED felica_get_container_property_request_t;
typedef struct {
felica_frame_response_noidm_t frame_response;
uint8_t property_data[];
} PACKED felica_get_container_property_response_t;
typedef struct {
uint8_t length[1];
uint8_t command_code[1];
uint8_t IDm[8];
uint8_t reserved[2];
} PACKED felica_get_container_issue_info_request_t;
typedef struct {
felica_frame_response_t frame_response;
uint8_t format_version_carrier_information[5];
uint8_t mobile_phone_model_information[11];
} PACKED felica_get_container_issue_info_response_t;
typedef struct {
uint8_t length[1];
uint8_t command_code[1];
uint8_t IDm[8];
} PACKED felica_get_platform_info_request_t;
typedef struct {
felica_frame_response_t frame_response;
uint8_t node_number[1];
+12
View File
@@ -849,12 +849,21 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
#define FELICA_WRTSEC_REQ 0x16
#define FELICA_WRTSEC_ACK 0x17
#define FELICA_GET_CONTAINER_ISSUE_INFO_REQ 0x22
#define FELICA_GET_CONTAINER_ISSUE_INFO_ACK 0x23
#define FELICA_GET_CONTAINER_PROPERTY_REQ 0x2e
#define FELICA_GET_CONTAINER_PROPERTY_ACK 0x2f
#define FELICA_REQSRV2_REQ 0x32
#define FELICA_REQSRV2_ACK 0x33
#define FELICA_GETSTATUS_REQ 0x38
#define FELICA_GETSTATUS_ACK 0x39
#define FELICA_GETPLATFORMINFO_REQ 0x3a
#define FELICA_GETPLATFORMINFO_ACK 0x3b
#define FELICA_OSVER_REQ 0x3c
#define FELICA_OSVER_ACK 0x3d
@@ -875,6 +884,9 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
#define FELICA_UPDATE_RNDID_REQ 0x4C
#define FELICA_UPDATE_RNDID_ACK 0x4D
#define FELICA_GET_CONTAINER_ID_REQ 0x70
#define FELICA_GET_CONTAINER_ID_ACK 0x71
// FeliCa SYSTEM list
#define SYSTEMCODE_ANY 0xffff // ANY
#define SYSTEMCODE_FELICA_LITE 0x88b4 // FeliCa Lite