mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-06-06 11:31:36 +00:00
Merge branch 'master' into master
Signed-off-by: Iceman <iceman@iuse.se>
This commit is contained in:
+2
-2
@@ -3,8 +3,8 @@ 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]
|
||||
- Improved `hf iclass legbrute` throughput: added a 64-wide bitsliced MAC1 sweep that tests 64 key candidates in parallel per cipher tick, with a scalar fallback for the non-aligned prefix/tail and for MAC2 verification on the rare MAC1 hits (@antiklesys ported, created by @chick3nman on the hashcat module)
|
||||
- Improved `hf iclass legbrute` throughput further: added wider SIMD backends (128-lane NEON, 256-lane AVX2, 512-lane AVX-512F) with a runtime dispatcher that picks the widest one the CPU supports and falls back to the portable 64-lane path elsewhere; the active backend and lane width are printed when the command starts (@antiklesys)
|
||||
- Improved `hf iclass legbrute` throughput: added a 64-wide bitsliced MAC1 sweep that tests 64 key candidates in parallel per cipher tick. Thanks @chick3nman (@antiklesys)
|
||||
- Improved `hf iclass legbrute` throughput further: added wider SIMD backends (128-lane NEON, 256-lane AVX2, 512-lane AVX-512F) with a runtime dispatcher that picks the widest one the CPU supports. Thanks @chick3nman (@antiklesys)
|
||||
|
||||
## [BREAKMEIFYOUCAN!.4.21611][2026-04-14]
|
||||
- Fixed `hf mf wrbl` and `hf mfp wrbl` the ACL RO checks on 16-block sectors correct (@team-orangeBlue)
|
||||
|
||||
@@ -141,10 +141,10 @@
|
||||
},
|
||||
{
|
||||
"code": "8182",
|
||||
"name": "QUICPay dedicated",
|
||||
"name": "ANA",
|
||||
"region": "Japan",
|
||||
"type": "payment",
|
||||
"description": "Dedicated system code for QUICPay"
|
||||
"description": "Used by ANA QuicPay + Nanaco fob"
|
||||
},
|
||||
{
|
||||
"code": "8194",
|
||||
|
||||
+258
-15
@@ -58,10 +58,18 @@
|
||||
#define FELICA_CONTAINER_PROPERTY_MAX_LEN 64U
|
||||
#define FELICA_OPTIONAL_CMD_TIMEOUT_MS 250U
|
||||
#define FELICA_OPTIONAL_CMD_RETRIES 3U
|
||||
// Per FeliCa spec, Polling max response at 16 timeslots is ~25ms; keep extra margin.
|
||||
#define FELICA_POLL_TIMEOUT_MS 50U
|
||||
#define FELICA_SEAC_POLL_TIMEOUT_MS 200U
|
||||
#define FELICA_SEAC_POLL_RETRY_COUNT 5U
|
||||
#define FELICA_SEAC_POLL_FRAME_LEN 5U
|
||||
#define FELICA_SYSTEM_CODE_MAX_COUNT 16U
|
||||
#define FELICA_DISCOVERED_SYSTEM_MAX_COUNT 4U
|
||||
#define FELICA_SYSTEM_CODE_WILDCARD 0xFFFFU
|
||||
#define FELICA_SYSTEM_CODE_NFC_TYPE3 0x12FCU
|
||||
#define FELICA_SYSTEM_CODE_FELICA_LITE 0x88B4U
|
||||
#define FELICA_POLL_REQUEST_NO_DATA 0x00U
|
||||
#define FELICA_POLL_REQUEST_SYSTEM_CODE 0x01U
|
||||
#define FELICA_SYSTEM_LIST_JSON "felica_system_code_list"
|
||||
|
||||
#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001)
|
||||
@@ -211,6 +219,17 @@ typedef struct {
|
||||
uint32_t public_service_count;
|
||||
} felica_dump_context_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t system_code;
|
||||
uint8_t idm[8];
|
||||
bool has_idm;
|
||||
} felica_discovered_system_t;
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
felica_discovered_system_t systems[FELICA_DISCOVERED_SYSTEM_MAX_COUNT];
|
||||
} felica_discovered_system_list_t;
|
||||
|
||||
typedef enum {
|
||||
FELICA_IDM_RESOLVE_STANDALONE = 0,
|
||||
FELICA_IDM_RESOLVE_CHAINED,
|
||||
@@ -679,6 +698,43 @@ static void felica_print_system_code_annotation(int level, const uint8_t *system
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_print_discovered_system_block(int level, const felica_discovered_system_t *system) {
|
||||
if (system == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const json_t *entry = felica_find_system_annotation(system->system_code);
|
||||
const char *name = NULL;
|
||||
const char *region = NULL;
|
||||
const char *card_type = NULL;
|
||||
|
||||
if (entry) {
|
||||
name = felica_get_json_string(entry, "name");
|
||||
region = felica_get_json_string(entry, "region");
|
||||
card_type = felica_get_json_string(entry, "type");
|
||||
}
|
||||
|
||||
if (name == NULL) {
|
||||
PrintAndLogEx(level, " " _YELLOW_("UNKNOWN") " (" _RED_("REPORT TO ICEMAN") ")");
|
||||
} else if (region && card_type) {
|
||||
PrintAndLogEx(level, " %s (" _YELLOW_("%s, %s") ")", name, region, card_type);
|
||||
} else if (region) {
|
||||
PrintAndLogEx(level, " %s (" _YELLOW_("%s") ")", name, region);
|
||||
} else if (card_type) {
|
||||
PrintAndLogEx(level, " %s (" _YELLOW_("%s") ")", name, card_type);
|
||||
} else {
|
||||
PrintAndLogEx(level, " %s", name);
|
||||
}
|
||||
|
||||
PrintAndLogEx(level, " System Code.... " _YELLOW_("%04X"), system->system_code);
|
||||
if (system->has_idm) {
|
||||
PrintAndLogEx(level, " IDM............ " _GREEN_("%s"),
|
||||
sprint_hex_inrow(system->idm, sizeof(system->idm)));
|
||||
} else {
|
||||
PrintAndLogEx(level, " IDM............ " _RED_("N/A"));
|
||||
}
|
||||
}
|
||||
|
||||
static int send_request_system_code(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose,
|
||||
uint32_t timeout_ms, uint32_t retries, bool logging,
|
||||
felica_syscode_response_t *system_code_response) {
|
||||
@@ -728,6 +784,202 @@ static int send_request_system_code(uint8_t flags, uint16_t datalen, uint8_t *da
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static uint16_t felica_system_code_from_bytes(const uint8_t *system_code_bytes) {
|
||||
if (system_code_bytes == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (uint16_t)((uint16_t)system_code_bytes[0] << 8) | system_code_bytes[1];
|
||||
}
|
||||
|
||||
static void felica_system_code_to_bytes(uint16_t system_code, uint8_t *system_code_bytes_out) {
|
||||
if (system_code_bytes_out == NULL) {
|
||||
return;
|
||||
}
|
||||
system_code_bytes_out[0] = (uint8_t)((system_code >> 8) & 0xFFU);
|
||||
system_code_bytes_out[1] = (uint8_t)(system_code & 0xFFU);
|
||||
}
|
||||
|
||||
static bool felica_add_unique_discovered_system(felica_discovered_system_t *systems, size_t *count,
|
||||
uint16_t system_code, const uint8_t *idm) {
|
||||
if (systems == NULL || count == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < *count; i++) {
|
||||
if (systems[i].system_code != system_code) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idm) {
|
||||
memcpy(systems[i].idm, idm, sizeof(systems[i].idm));
|
||||
systems[i].has_idm = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*count >= FELICA_DISCOVERED_SYSTEM_MAX_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
felica_discovered_system_t *system = &systems[*count];
|
||||
memset(system, 0, sizeof(*system));
|
||||
system->system_code = system_code;
|
||||
if (idm) {
|
||||
memcpy(system->idm, idm, sizeof(system->idm));
|
||||
system->has_idm = true;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int send_polling(uint8_t flags, uint16_t system_code, uint8_t request_code,
|
||||
uint32_t timeout_ms, uint32_t retries, bool logging,
|
||||
uint8_t *idm_out, uint16_t *returned_system_code) {
|
||||
if (idm_out == NULL) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
uint8_t polling_request[6] = {0};
|
||||
polling_request[0] = sizeof(polling_request);
|
||||
polling_request[1] = FELICA_POLL_REQ;
|
||||
felica_system_code_to_bytes(system_code, polling_request + 2);
|
||||
polling_request[4] = request_code;
|
||||
polling_request[5] = 0x00; // one target at a time
|
||||
|
||||
PacketResponseNG resp;
|
||||
if (send_felica_payload_with_retries(flags, sizeof(polling_request), polling_request, false,
|
||||
FELICA_POLL_ACK,
|
||||
timeout_ms, retries,
|
||||
0, logging, &resp, "polling") != PM3_SUCCESS) {
|
||||
return PM3_ERFTRANS;
|
||||
}
|
||||
|
||||
static const size_t poll_response_min_len = 22U;
|
||||
static const size_t poll_response_idm_offset = 4U;
|
||||
static const size_t poll_response_system_code_offset = 20U;
|
||||
|
||||
if (resp.length < poll_response_min_len) {
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
memcpy(idm_out, resp.data.asBytes + poll_response_idm_offset, 8);
|
||||
|
||||
if (returned_system_code) {
|
||||
if (request_code == FELICA_POLL_REQUEST_SYSTEM_CODE) {
|
||||
if (resp.length < (poll_response_min_len + 2U)) {
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
*returned_system_code = felica_system_code_from_bytes(resp.data.asBytes + poll_response_system_code_offset);
|
||||
} else {
|
||||
*returned_system_code = system_code;
|
||||
}
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int discover_systems(uint8_t flags, const uint8_t *primary_idm,
|
||||
felica_discovered_system_list_t *discovered_systems) {
|
||||
if (primary_idm == NULL || discovered_systems == NULL) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
memset(discovered_systems, 0, sizeof(*discovered_systems));
|
||||
|
||||
felica_request_system_code_request_t request_system_code_request;
|
||||
memset(&request_system_code_request, 0, sizeof(request_system_code_request));
|
||||
request_system_code_request.length[0] = sizeof(request_system_code_request);
|
||||
request_system_code_request.command_code[0] = FELICA_REQSYSCODE_REQ;
|
||||
memcpy(request_system_code_request.IDm, primary_idm, sizeof(request_system_code_request.IDm));
|
||||
|
||||
felica_syscode_response_t system_code_response;
|
||||
const int request_system_code_status = send_request_system_code(flags,
|
||||
sizeof(request_system_code_request), (uint8_t *)&request_system_code_request,
|
||||
false, FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES, false,
|
||||
&system_code_response);
|
||||
|
||||
if (request_system_code_status == PM3_SUCCESS) {
|
||||
const size_t reported_systems = system_code_response.number_of_systems[0];
|
||||
for (size_t i = 0; i < reported_systems; i++) {
|
||||
const uint16_t system_code = felica_system_code_from_bytes(system_code_response.system_code_list + (i * 2U));
|
||||
if (felica_add_unique_discovered_system(discovered_systems->systems, &discovered_systems->count,
|
||||
system_code, NULL) == false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for cards that do not support Request System Code.
|
||||
if (discovered_systems->count == 0) {
|
||||
uint16_t primary_system_code = 0;
|
||||
uint8_t primary_idm_polled[8] = {0};
|
||||
if (send_polling(flags, FELICA_SYSTEM_CODE_WILDCARD, FELICA_POLL_REQUEST_SYSTEM_CODE,
|
||||
FELICA_POLL_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES, false,
|
||||
primary_idm_polled, &primary_system_code) != PM3_SUCCESS) {
|
||||
return PM3_ERFTRANS;
|
||||
}
|
||||
|
||||
felica_add_unique_discovered_system(discovered_systems->systems, &discovered_systems->count,
|
||||
primary_system_code, primary_idm_polled);
|
||||
|
||||
if (primary_system_code == FELICA_SYSTEM_CODE_NFC_TYPE3 ||
|
||||
primary_system_code == FELICA_SYSTEM_CODE_FELICA_LITE) {
|
||||
const uint16_t alternate_system_code =
|
||||
(primary_system_code == FELICA_SYSTEM_CODE_NFC_TYPE3)
|
||||
? FELICA_SYSTEM_CODE_FELICA_LITE
|
||||
: FELICA_SYSTEM_CODE_NFC_TYPE3;
|
||||
|
||||
uint8_t alternate_idm_polled[8] = {0};
|
||||
if (send_polling(flags, alternate_system_code, FELICA_POLL_REQUEST_NO_DATA,
|
||||
FELICA_POLL_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES, false,
|
||||
alternate_idm_polled, NULL) == PM3_SUCCESS) {
|
||||
felica_add_unique_discovered_system(discovered_systems->systems, &discovered_systems->count,
|
||||
alternate_system_code, alternate_idm_polled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discovered_systems->count == 0) {
|
||||
return PM3_ERFTRANS;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < discovered_systems->count; i++) {
|
||||
uint8_t resolved_idm[8] = {0};
|
||||
if (send_polling(flags,
|
||||
discovered_systems->systems[i].system_code,
|
||||
FELICA_POLL_REQUEST_NO_DATA,
|
||||
FELICA_POLL_TIMEOUT_MS,
|
||||
FELICA_OPTIONAL_CMD_RETRIES,
|
||||
false,
|
||||
resolved_idm, NULL) != PM3_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(discovered_systems->systems[i].idm, resolved_idm, sizeof(discovered_systems->systems[i].idm));
|
||||
discovered_systems->systems[i].has_idm = true;
|
||||
}
|
||||
|
||||
size_t write_index = 0;
|
||||
for (size_t i = 0; i < discovered_systems->count; i++) {
|
||||
if (discovered_systems->systems[i].has_idm == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (write_index != i) {
|
||||
discovered_systems->systems[write_index] = discovered_systems->systems[i];
|
||||
}
|
||||
write_index++;
|
||||
}
|
||||
discovered_systems->count = write_index;
|
||||
|
||||
if (discovered_systems->count == 0) {
|
||||
return PM3_ERFTRANS;
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for response from pm3 or timeout.
|
||||
* Checks if receveid bytes have a valid CRC.
|
||||
@@ -1320,21 +1572,12 @@ static int info_felica(bool verbose) {
|
||||
}
|
||||
}
|
||||
|
||||
felica_request_system_code_request_t request_system_code_request;
|
||||
memset(&request_system_code_request, 0, sizeof(request_system_code_request));
|
||||
request_system_code_request.length[0] = sizeof(request_system_code_request);
|
||||
request_system_code_request.command_code[0] = FELICA_REQSYSCODE_REQ;
|
||||
memcpy(request_system_code_request.IDm, card.IDm, sizeof(request_system_code_request.IDm));
|
||||
|
||||
felica_syscode_response_t system_code_response;
|
||||
if (send_request_system_code(optional_flags,
|
||||
sizeof(request_system_code_request), (uint8_t *)&request_system_code_request,
|
||||
false, FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES, false,
|
||||
&system_code_response) == PM3_SUCCESS &&
|
||||
system_code_response.number_of_systems[0] > 0) {
|
||||
PrintAndLogEx(INFO, "System codes.... " _GREEN_("%u"), system_code_response.number_of_systems[0]);
|
||||
for (size_t i = 0; i < system_code_response.number_of_systems[0]; i++) {
|
||||
felica_print_system_code_annotation(INFO, system_code_response.system_code_list + (i * 2));
|
||||
felica_discovered_system_list_t discovered_systems;
|
||||
if (discover_systems(optional_flags, card.IDm, &discovered_systems) == PM3_SUCCESS &&
|
||||
discovered_systems.count > 0) {
|
||||
PrintAndLogEx(INFO, "Systems........ " _GREEN_("%u:"), (unsigned int)discovered_systems.count);
|
||||
for (size_t i = 0; i < discovered_systems.count; i++) {
|
||||
felica_print_discovered_system_block(INFO, &discovered_systems.systems[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
#define MFDES_BRUTEFID_RESELECT_WAIT_MS 500
|
||||
#define MFDES_BRUTEDAMSLOT_RESELECT_ATTEMPTS 3
|
||||
#define MFDES_BRUTEDAMSLOT_RESELECT_WAIT_MS 500
|
||||
#define MFDES_PC_KEY_LEN 16U
|
||||
#define MFDES_PC_CHALLENGE_LEN 8U
|
||||
#define MFDES_PC_MAX_ROUNDS 8U
|
||||
#define MFDES_PC_MAC_LEN 8U
|
||||
|
||||
#define status(x) ( ((uint16_t)(0x91 << 8)) + (uint16_t)x )
|
||||
/*
|
||||
@@ -669,6 +673,191 @@ static int CmdDesGetSessionParameters(CLIParserContext *ctx, DesfireContext_t *d
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int DesfirePCRun(DesfireContext_t *dctx, const uint8_t proximity_key[MFDES_PC_KEY_LEN], uint8_t rounds, bool verbose) {
|
||||
if (dctx == NULL || proximity_key == NULL || rounds < 1 || rounds > MFDES_PC_MAX_ROUNDS) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
int res = PM3_SUCCESS;
|
||||
pcrypto_rng_t rng = {0};
|
||||
bool rng_initialized = false;
|
||||
|
||||
const uint8_t rng_personalization[] = "hf_mfdes_pc";
|
||||
res = pcrypto_rng_init(&rng, rng_personalization, sizeof(rng_personalization) - 1);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to initialize random generator for proximity check");
|
||||
return res;
|
||||
}
|
||||
rng_initialized = true;
|
||||
|
||||
uint8_t prepare_resp[APDU_RES_LEN] = {0};
|
||||
size_t prepare_resp_len = 0;
|
||||
uint8_t respcode = 0xFF;
|
||||
res = DesfireExchangeEx(true, dctx, MFDES_PREPARE_PC, NULL, 0, &respcode, prepare_resp, &prepare_resp_len, true, 0);
|
||||
if (res != PM3_SUCCESS) {
|
||||
uint16_t sw = status(respcode);
|
||||
PrintAndLogEx(ERR, "Prepare proximity check command failed. Result: %d %s", res, DesfireGetErrorString(res, &sw));
|
||||
goto out;
|
||||
}
|
||||
|
||||
uint8_t options[3] = {0};
|
||||
size_t options_len = MIN((size_t)3, prepare_resp_len);
|
||||
if (options_len > 0) {
|
||||
memcpy(options, prepare_resp, options_len);
|
||||
}
|
||||
|
||||
bool prepare_extension_present = (prepare_resp_len > 3);
|
||||
uint8_t prepare_extension = prepare_extension_present ? prepare_resp[3] : 0x00;
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Prepare response [%zu] : %s", prepare_resp_len, sprint_hex(prepare_resp, prepare_resp_len));
|
||||
PrintAndLogEx(INFO, "Prepare options[%zu] : %s", options_len, sprint_hex(options, options_len));
|
||||
if (prepare_extension_present) {
|
||||
PrintAndLogEx(INFO, "Prepare extension : %02X", prepare_extension);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t random_challenge[MFDES_PC_CHALLENGE_LEN] = {0};
|
||||
res = pcrypto_rng_fill(&rng, random_challenge, sizeof(random_challenge));
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to generate random challenge");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Random challenge : %s", sprint_hex(random_challenge, sizeof(random_challenge)));
|
||||
}
|
||||
|
||||
size_t split_size = MFDES_PC_CHALLENGE_LEN / rounds;
|
||||
if (split_size == 0) {
|
||||
split_size = 1;
|
||||
}
|
||||
|
||||
uint8_t challenge_response[MFDES_PC_CHALLENGE_LEN * 2] = {0};
|
||||
size_t challenge_response_len = 0;
|
||||
size_t challenge_offset = 0;
|
||||
|
||||
for (uint8_t round = 0; round < rounds; round++) {
|
||||
size_t remaining = MFDES_PC_CHALLENGE_LEN - challenge_offset;
|
||||
size_t challenge_part_len = ((round + 1) == rounds) ? remaining : MIN(split_size, remaining);
|
||||
if (challenge_part_len == 0) {
|
||||
PrintAndLogEx(ERR, "Invalid challenge split state at round %u", (unsigned int)round + 1);
|
||||
res = PM3_ESOFT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
uint8_t round_payload[1 + MFDES_PC_CHALLENGE_LEN] = {0};
|
||||
round_payload[0] = (uint8_t)challenge_part_len;
|
||||
memcpy(&round_payload[1], &random_challenge[challenge_offset], challenge_part_len);
|
||||
|
||||
uint8_t round_resp[APDU_RES_LEN] = {0};
|
||||
size_t round_resp_len = 0;
|
||||
respcode = 0xFF;
|
||||
|
||||
res = DesfireExchange(dctx, MFDES_PROXIMITY_CHECK, round_payload, challenge_part_len + 1, &respcode, round_resp, &round_resp_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
uint16_t sw = status(respcode);
|
||||
PrintAndLogEx(ERR, "Proximity check round %u command failed. Result: %d %s", (unsigned int)round + 1, res, DesfireGetErrorString(res, &sw));
|
||||
goto out;
|
||||
}
|
||||
|
||||
size_t response_slice_len = MIN(round_resp_len, challenge_part_len);
|
||||
memcpy(&challenge_response[challenge_response_len], round_resp, response_slice_len);
|
||||
challenge_response_len += response_slice_len;
|
||||
memcpy(&challenge_response[challenge_response_len], &random_challenge[challenge_offset], challenge_part_len);
|
||||
challenge_response_len += challenge_part_len;
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Round %u challenge[%zu]: %s", (unsigned int)round + 1, challenge_part_len, sprint_hex(&random_challenge[challenge_offset], challenge_part_len));
|
||||
PrintAndLogEx(INFO, "Round %u response [%zu]: %s", (unsigned int)round + 1, round_resp_len, sprint_hex(round_resp, round_resp_len));
|
||||
}
|
||||
|
||||
challenge_offset += challenge_part_len;
|
||||
}
|
||||
|
||||
bool include_prepare_extension = prepare_extension_present;
|
||||
|
||||
uint8_t verify_cmd_input[1 + 3 + 1 + (MFDES_PC_CHALLENGE_LEN * 2)] = {0};
|
||||
size_t verify_cmd_input_len = 0;
|
||||
verify_cmd_input[verify_cmd_input_len++] = MFDES_VERIFY_PC;
|
||||
if (options_len > 0) {
|
||||
memcpy(&verify_cmd_input[verify_cmd_input_len], options, options_len);
|
||||
verify_cmd_input_len += options_len;
|
||||
}
|
||||
if (include_prepare_extension) {
|
||||
verify_cmd_input[verify_cmd_input_len++] = prepare_extension;
|
||||
}
|
||||
memcpy(&verify_cmd_input[verify_cmd_input_len], challenge_response, challenge_response_len);
|
||||
verify_cmd_input_len += challenge_response_len;
|
||||
|
||||
uint8_t verify_cmd_mac[MFDES_PC_MAC_LEN] = {0};
|
||||
res = aes_cmac8(NULL, (uint8_t *)proximity_key, verify_cmd_input, verify_cmd_mac, (int)verify_cmd_input_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to calculate Verify_PC command MAC");
|
||||
res = PM3_ESOFT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Verify command input[%zu]: %s", verify_cmd_input_len, sprint_hex(verify_cmd_input, verify_cmd_input_len));
|
||||
PrintAndLogEx(INFO, "Verify command MAC : %s", sprint_hex(verify_cmd_mac, sizeof(verify_cmd_mac)));
|
||||
}
|
||||
|
||||
uint8_t verify_resp[APDU_RES_LEN] = {0};
|
||||
size_t verify_resp_len = 0;
|
||||
respcode = 0xFF;
|
||||
res = DesfireExchange(dctx, MFDES_VERIFY_PC, verify_cmd_mac, sizeof(verify_cmd_mac), &respcode, verify_resp, &verify_resp_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
uint16_t sw = status(respcode);
|
||||
PrintAndLogEx(ERR, "Verify proximity check command failed. Result: %d %s", res, DesfireGetErrorString(res, &sw));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (verify_resp_len < MFDES_PC_MAC_LEN) {
|
||||
PrintAndLogEx(WARNING, "Verify response MAC is not present");
|
||||
} else {
|
||||
uint8_t verify_resp_input[1 + 3 + 1 + (MFDES_PC_CHALLENGE_LEN * 2)] = {0};
|
||||
size_t verify_resp_input_len = 0;
|
||||
verify_resp_input[verify_resp_input_len++] = MFDES_S_SIGNATURE;
|
||||
if (options_len > 0) {
|
||||
memcpy(&verify_resp_input[verify_resp_input_len], options, options_len);
|
||||
verify_resp_input_len += options_len;
|
||||
}
|
||||
if (prepare_extension_present) {
|
||||
verify_resp_input[verify_resp_input_len++] = prepare_extension;
|
||||
}
|
||||
memcpy(&verify_resp_input[verify_resp_input_len], challenge_response, challenge_response_len);
|
||||
verify_resp_input_len += challenge_response_len;
|
||||
|
||||
uint8_t expected_resp_mac[MFDES_PC_MAC_LEN] = {0};
|
||||
res = aes_cmac8(NULL, (uint8_t *)proximity_key, verify_resp_input, expected_resp_mac, (int)verify_resp_input_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to calculate expected verify response MAC");
|
||||
res = PM3_ESOFT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (memcmp(verify_resp, expected_resp_mac, MFDES_PC_MAC_LEN) != 0) {
|
||||
PrintAndLogEx(WARNING, "Verify response MAC mismatch");
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Expected: %s", sprint_hex(expected_resp_mac, MFDES_PC_MAC_LEN));
|
||||
PrintAndLogEx(INFO, "Received: %s", sprint_hex(verify_resp, MFDES_PC_MAC_LEN));
|
||||
}
|
||||
} else if (verbose) {
|
||||
PrintAndLogEx(INFO, "Verify response MAC : %s", sprint_hex(verify_resp, MFDES_PC_MAC_LEN));
|
||||
}
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, "DESFire proximity check " _GREEN_("passed") ", rounds: %u", rounds);
|
||||
res = PM3_SUCCESS;
|
||||
|
||||
out:
|
||||
if (rng_initialized) {
|
||||
pcrypto_rng_free(&rng);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdHF14ADesDefault(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf mfdes default",
|
||||
@@ -4331,6 +4520,70 @@ static int CmdHF14ADesGetUID(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdHF14ADesPC(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf mfdes pc",
|
||||
"Perform DESFire proximity check flow (Prepare_PC -> Proximity_Check -> Verify_PC).\n"
|
||||
"This command uses plain communication with a dedicated 16-byte AES proximity key.",
|
||||
"hf mfdes pc --key 00000000000000000000000000000000\n"
|
||||
"hf mfdes pc --key 00112233445566778899aabbccddeeff --rounds 4\n"
|
||||
"hf mfdes pc --key 00112233445566778899aabbccddeeff -c native -a");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_lit0("a", "apdu", "Show APDU requests and responses"),
|
||||
arg_lit0("v", "verbose", "Verbose output"),
|
||||
arg_str1("k", "key", "<hex>", "Key (AES-128, exactly 16 bytes)"),
|
||||
arg_int0("r", "rounds", "<dec>", "Number of rounds (1..8), default 8"),
|
||||
arg_str0("c", "ccset", "<native|niso>", "Communication command set (default from `hf mfdes default`)"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
bool APDULogging = arg_get_lit(ctx, 1);
|
||||
bool verbose = arg_get_lit(ctx, 2);
|
||||
|
||||
uint8_t proximity_key[MFDES_PC_KEY_LEN] = {0};
|
||||
int proximity_key_len = 0;
|
||||
CLIGetHexWithReturn(ctx, 3, proximity_key, &proximity_key_len);
|
||||
if (proximity_key_len != MFDES_PC_KEY_LEN) {
|
||||
PrintAndLogEx(ERR, "Proximity key must have 16 bytes length.");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
int rounds = arg_get_int_def(ctx, 4, MFDES_PC_MAX_ROUNDS);
|
||||
if (rounds < 1 || rounds > MFDES_PC_MAX_ROUNDS) {
|
||||
PrintAndLogEx(ERR, "Rounds must be in range 1..8.");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
int cmdset = defaultCommSet;
|
||||
if (CLIGetOptionList(arg_get_str(ctx, 5), DesfireCommandSetOpts, &cmdset)) {
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (cmdset == DCCISO) {
|
||||
PrintAndLogEx(ERR, "`ccset=iso` is not supported for this command. Use `native` or `niso`.");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
SetAPDULogging(APDULogging);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
DesfireContext_t dctx = {0};
|
||||
DesfireSetCommandSet(&dctx, cmdset);
|
||||
DesfireSetCommMode(&dctx, DCMPlain);
|
||||
DesfireSetSecureChannel(&dctx, DACNone);
|
||||
|
||||
int res = DesfirePCRun(&dctx, proximity_key, rounds, verbose);
|
||||
DropField();
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdHF14ADesFormatPICC(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf mfdes formatpicc",
|
||||
@@ -7286,6 +7539,7 @@ static command_t CommandTable[] = {
|
||||
{"formatpicc", CmdHF14ADesFormatPICC, IfPm3Iso14443a, "Format PICC"},
|
||||
{"freemem", CmdHF14ADesGetFreeMem, IfPm3Iso14443a, "Get free memory size"},
|
||||
{"getuid", CmdHF14ADesGetUID, IfPm3Iso14443a, "Get uid from card"},
|
||||
{"pc", CmdHF14ADesPC, IfPm3Iso14443a, "Run proximity check"},
|
||||
{"info", CmdHF14ADesInfo, IfPm3Iso14443a, "Tag information"},
|
||||
{"mad", CmdHF14aDesMAD, IfPm3Iso14443a, "Prints MAD records / files from the card"},
|
||||
{"setconfig", CmdHF14ADesSetConfiguration, IfPm3Iso14443a, "Set card configuration"},
|
||||
|
||||
@@ -522,22 +522,43 @@ static int DESFIRESendRaw(bool activate_field, uint8_t *data, size_t datalen, ui
|
||||
}
|
||||
|
||||
if (*result_len < (1 + 2)) {
|
||||
// In native mode of communication, DESFire cards don't seem to return
|
||||
// a status byte for PROXIMITY_CHECK.
|
||||
if (data[0] == MFDES_PROXIMITY_CHECK &&
|
||||
*result_len >= 2) {
|
||||
*result_len -= 2; // strip CRC only
|
||||
if (respcode) {
|
||||
*respcode = MFDES_S_OPERATION_OK;
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
*result_len -= (1 + 2);
|
||||
|
||||
uint8_t rcode = result[0];
|
||||
bool rcode_ok = (rcode == MFDES_S_OPERATION_OK ||
|
||||
rcode == MFDES_S_SIGNATURE ||
|
||||
rcode == MFDES_S_ADDITIONAL_FRAME ||
|
||||
rcode == MFDES_S_NO_CHANGES);
|
||||
|
||||
if (rcode_ok == false &&
|
||||
data[0] == MFDES_PROXIMITY_CHECK) {
|
||||
// Proximity-check response without native status byte:
|
||||
// current *result_len is (rawlen - 3), but we only need to strip CRC.
|
||||
*result_len += 1;
|
||||
if (respcode) {
|
||||
*respcode = MFDES_S_OPERATION_OK;
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
if (respcode) {
|
||||
*respcode = rcode;
|
||||
}
|
||||
|
||||
memmove(&result[0], &result[1], *result_len);
|
||||
|
||||
if (rcode != MFDES_S_OPERATION_OK &&
|
||||
rcode != MFDES_S_SIGNATURE &&
|
||||
rcode != MFDES_S_ADDITIONAL_FRAME &&
|
||||
rcode != MFDES_S_NO_CHANGES) {
|
||||
if (!rcode_ok) {
|
||||
|
||||
if (GetAPDULogging()) {
|
||||
PrintAndLogEx(ERR, "Command (%02x) ERROR: 0x%02x", data[0], rcode);
|
||||
@@ -545,6 +566,8 @@ static int DESFIRESendRaw(bool activate_field, uint8_t *data, size_t datalen, ui
|
||||
|
||||
return PM3_EAPDU_FAIL;
|
||||
}
|
||||
|
||||
memmove(&result[0], &result[1], *result_len);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -489,6 +489,7 @@ const static vocabulary_t vocabulary[] = {
|
||||
{ 0, "hf mfdes formatpicc" },
|
||||
{ 0, "hf mfdes freemem" },
|
||||
{ 0, "hf mfdes getuid" },
|
||||
{ 0, "hf mfdes pc" },
|
||||
{ 0, "hf mfdes info" },
|
||||
{ 0, "hf mfdes mad" },
|
||||
{ 0, "hf mfdes setconfig" },
|
||||
|
||||
Reference in New Issue
Block a user