From 6b315af2bba3e5de36e4405ea4cf99b31c1ce6ae Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:18:24 +0300 Subject: [PATCH 1/5] Introduce 'felica_system_code_list.json' resource for FeliCa system codes --- .gitignore | 1 + client/resources/felica_system_code_list.json | 114 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 client/resources/felica_system_code_list.json diff --git a/.gitignore b/.gitignore index 6638349c7..85cf9902c 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ cov-int/ !client/resources/hardnested/*.bin !client/resources/hardnested_tables/*.z +!client/resources/felica_system_code_list.json client/src/ui/ui_image.h client/src/ui/ui_overlays.h client/deps/reveng/bmptst diff --git a/client/resources/felica_system_code_list.json b/client/resources/felica_system_code_list.json new file mode 100644 index 000000000..e048c9de7 --- /dev/null +++ b/client/resources/felica_system_code_list.json @@ -0,0 +1,114 @@ +[ + { + "code": "0003", + "name": "CJRC Standard", + "region": "Japan", + "type": "transit", + "description": "Used by Mutual Use transit cards" + }, + { + "code": "0102", + "name": "EZLink", + "region": "Singapore", + "type": "transit", + "description": "Used by legacy EZ-Link transit cards" + }, + { + "code": "04C7", + "name": "nanaco", + "region": "Japan", + "type": "payment", + "description": "nanaco e-money system" + }, + { + "code": "12FC", + "name": "NDEF Type 3 Tag", + "region": "Global", + "type": "ndef", + "description": "NFC Forum Type 3 Tag system code" + }, + { + "code": "8005", + "name": "Shenzhen Tong", + "region": "China", + "type": "transit", + "description": "Used by legacy Shenzen Tong cards" + }, + { + "code": "8008", + "name": "Octopus", + "region": "Hong Kong", + "type": "transit", + "description": "Used by Octopus transit cards" + }, + { + "code": "852B", + "name": "WAON", + "region": "Japan", + "type": "payment", + "description": "Can be found on some WAON cards" + }, + { + "code": "8592", + "name": "PASPY", + "region": "Japan", + "type": "transit", + "description": "PASPY transit card system" + }, + { + "code": "86A7", + "name": "Suica", + "region": "Japan", + "type": "transit", + "description": "JR East Suica and related transit cards" + }, + { + "code": "88B4", + "name": "FeliCa Lite", + "region": "Global", + "type": "common", + "description": "Default system code used by FeliCa Lite/Lite-S tags" + }, + { + "code": "90B7", + "name": "Kartu Multi Trip Commet", + "region": "Indonesia", + "type": "transit", + "description": "Used on Kartu Multi Trip Commet cards" + }, + { + "code": "9373", + "name": "Kartu Multi Trip Jelajah", + "region": "Indonesia", + "type": "transit", + "description": "Used on Kartu Multi Trip Jelajah cards" + }, + { + "code": "9450", + "name": "HCMC", + "region": "Vietnam", + "type": "transit", + "description": "Used with Ho Chi Minh City Metro transit cards" + }, + { + "code": "FE00", + "name": "FeliCa Networks Common Area", + "region": "Japan", + "type": "common", + "description": "Common area for FeliCa Networks services" + }, + { + "code": "FE0F", + "name": "Osaifu Keitai Container", + "region": "Japan", + "type": "mobile", + "description": "Container system used by devices with Osaifu-Keitai" + }, + { + "code": "FFFF", + "name": "Unfused system", + "region": "Global", + "type": "unfused", + "description": "Default unfused/pre-personalization system" + } +] From 3eecb97a88ff465f586e7f7b8b6b694566453e4a Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:25:14 +0300 Subject: [PATCH 2/5] Add system code annotation for 'hf felica info' and 'hf felica rqsyscode' --- CHANGELOG.md | 1 + client/src/cmdhffelica.c | 216 +++++++++++++++++++++++++++++++++++---- include/iso18.h | 6 ++ 3 files changed, 201 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a40a173..21bbcedb4 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 system code name annotation to 'hf felica info' and 'hf felica rqsyscode' - Added `lf relay` command where it relays between two pm3 devices over internet. Thanks to Moerno for the code! (@iceman1001) - Changed `hf mf acl` command to have more recognized generic configurations (@team-orangeBlue) - Added `hf mfp acl` command (@team-orangeBlue) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 6926582a2..0e4ad235a 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -26,6 +26,7 @@ #include "cmdtrace.h" #include "crc16.h" #include "util.h" +#include "fileutils.h" #include "commonutil.h" // ARRAYLEN #include "ui.h" #include "iso18.h" // felica_card_select_t struct @@ -60,6 +61,8 @@ #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_SYSTEM_LIST_JSON "felica_system_code_list" #define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) #define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) @@ -253,6 +256,8 @@ static const char *felica_node_discovery_method_display_name(felica_node_discove static void felica_print_node_discovery_method_used(felica_node_discovery_method_t method); static int felica_compare_discovered_nodes(const void *lhs, const void *rhs); static felica_card_select_t last_known_card; +static json_t *felica_system_list = NULL; +static bool felica_system_list_loaded = false; static void set_last_known_card(felica_card_select_t card) { last_known_card = card; @@ -568,6 +573,161 @@ static void print_platform_information(const uint8_t *platform_information_data, sprint_hex_inrow(platform_information_data, platform_information_data_len)); } +static json_t *felica_get_system_list(void) { + if (felica_system_list_loaded) { + return felica_system_list; + } + + felica_system_list_loaded = true; + + char *path = NULL; + if (searchFile(&path, RESOURCES_SUBDIR, FELICA_SYSTEM_LIST_JSON, ".json", true) != PM3_SUCCESS) { + return NULL; + } + + json_error_t error; + json_t *root = json_load_file(path, 0, &error); + if (root == NULL) { + PrintAndLogEx(WARNING, "Failed to parse `%s` line %d: %s", path, error.line, error.text); + free(path); + return NULL; + } + + if (json_is_array(root) == false) { + PrintAndLogEx(WARNING, "Invalid `%s` format, expected array root", path); + json_decref(root); + free(path); + return NULL; + } + + felica_system_list = root; + free(path); + return felica_system_list; +} + +static const char *felica_get_json_string(const json_t *obj, const char *key) { + json_t *value = json_object_get(obj, key); + if (json_is_string(value) == false) { + return NULL; + } + + const char *str = json_string_value(value); + if (str == NULL || str[0] == '\0') { + return NULL; + } + + return str; +} + +static const json_t *felica_find_system_annotation(uint16_t system_code) { + json_t *system_list = felica_get_system_list(); + if (system_list == NULL) { + return NULL; + } + + char code_hex[5] = {0}; + snprintf(code_hex, sizeof(code_hex), "%04X", system_code); + + size_t index = 0; + json_t *entry = NULL; + json_array_foreach(system_list, index, entry) { + if (json_is_object(entry) == false) { + continue; + } + + const char *entry_code = felica_get_json_string(entry, "code"); + if (entry_code && strcmp(entry_code, code_hex) == 0) { + return entry; + } + } + + return NULL; +} + +static void felica_print_system_code_annotation(int level, const uint8_t *system_code_bytes) { + if (system_code_bytes == NULL) { + return; + } + + uint16_t system_code = ((uint16_t)system_code_bytes[0] << 8) | system_code_bytes[1]; + char code_hex[5] = {0}; + snprintf(code_hex, sizeof(code_hex), "%04X", system_code); + + const json_t *entry = felica_find_system_annotation(system_code); + if (entry == NULL) { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. UNKNOWN (" _RED_("Report to Iceman!") ")", code_hex); + return; + } + + const char *name = felica_get_json_string(entry, "name"); + const char *region = felica_get_json_string(entry, "region"); + const char *card_type = felica_get_json_string(entry, "type"); + + if (name == NULL) { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. UNKNOWN (" _RED_("Report to Iceman!") ")", code_hex); + return; + } + + if (region && card_type) { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. %s (" _YELLOW_("%s, %s") ")", code_hex, name, region, card_type); + } else if (region) { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. %s (" _YELLOW_("%s") ")", code_hex, name, region); + } else if (card_type) { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. %s (" _YELLOW_("%s") ")", code_hex, name, card_type); + } else { + PrintAndLogEx(level, " " _YELLOW_("%s") "............. %s", code_hex, name); + } +} + +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) { + if (system_code_response == NULL) { + return PM3_EINVARG; + } + + memset(system_code_response, 0, sizeof(*system_code_response)); + + PacketResponseNG resp; + if (send_felica_payload_with_retries(flags, datalen, data, verbose, + FELICA_REQSYSCODE_ACK, + timeout_ms, retries, + 0, logging, &resp, "request system code") != PM3_SUCCESS) { + return PM3_ERFTRANS; + } + + if (resp.length < (sizeof(felica_frame_response_t) + 1 + 2)) { + return PM3_ESOFT; + } + + const size_t payload_length = resp.length - 2; + const size_t number_of_systems_offset = sizeof(felica_frame_response_t); + if (payload_length < (number_of_systems_offset + 1)) { + return PM3_ESOFT; + } + + uint8_t reported_system_count = resp.data.asBytes[number_of_systems_offset]; + const size_t available_system_count = (payload_length - number_of_systems_offset - 1) / 2; + if (reported_system_count > available_system_count) { + return PM3_ESOFT; + } + + if (reported_system_count > FELICA_SYSTEM_CODE_MAX_COUNT) { + return PM3_ESOFT; + } + + if (reported_system_count == 0) { + memcpy(system_code_response, resp.data.asBytes, sizeof(felica_frame_response_t) + 1); + return PM3_SUCCESS; + } + + memcpy(system_code_response, resp.data.asBytes, sizeof(felica_frame_response_t) + 1); + memcpy(system_code_response->system_code_list, + resp.data.asBytes + number_of_systems_offset + 1, + (size_t)reported_system_count * 2U); + return PM3_SUCCESS; +} + /** * Wait for response from pm3 or timeout. * Checks if receveid bytes have a valid CRC. @@ -1047,7 +1207,7 @@ static int info_felica(bool verbose) { memcpy(&card, (felica_card_select_t *)resp.data.asBytes, sizeof(felica_card_select_t)); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); - PrintAndLogEx(INFO, "IDm............ " _YELLOW_("%s"), sprint_hex_inrow(card.IDm, sizeof(card.IDm))); + PrintAndLogEx(INFO, "Primary IDm.... " _YELLOW_("%s"), sprint_hex_inrow(card.IDm, sizeof(card.IDm))); PrintAndLogEx(INFO, " Code......... " _GREEN_("%s"), sprint_hex_inrow(card.code, sizeof(card.code))); PrintAndLogEx(INFO, " NFCID2....... " _GREEN_("%s"), sprint_hex_inrow(card.uid, sizeof(card.uid))); PrintAndLogEx(INFO, "PMM............ " _YELLOW_("%s"), sprint_hex_inrow(card.PMm, sizeof(card.PMm))); @@ -1160,6 +1320,24 @@ 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)); + } + } + DropField(); PrintAndLogEx(NORMAL, ""); return PM3_SUCCESS; @@ -3391,38 +3569,32 @@ static int CmdHFFelicaRequestSystemCode(const char *Cmd) { CLIParserFree(ctx); - uint8_t data[PM3_CMD_DATA_SIZE]; - memset(data, 0, sizeof(data)); - data[0] = 0x0A; // Static length - data[1] = 0x0C; // Command ID + 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; - uint16_t datalen = 10; // Length (1), Command ID (1), IDm (8) - res = felica_ensure_target_present(idm, (size_t)ilen, FELICA_IDM_RESOLVE_STANDALONE, data + 2); + res = felica_ensure_target_present(idm, (size_t)ilen, FELICA_IDM_RESOLVE_STANDALONE, request_system_code_request.IDm); if (res != PM3_SUCCESS) { return res; } uint8_t flags = (FELICA_APPEND_CRC | FELICA_RAW); - - clear_and_send_command(flags, datalen, data, 0); - - PacketResponseNG resp; - if (waitCmdFelica(false, &resp, true) == false) { + felica_syscode_response_t system_code_response; + if (send_request_system_code(flags, + sizeof(request_system_code_request), (uint8_t *)&request_system_code_request, + false, + FELICA_DEFAULT_TIMEOUT_MS, FELICA_DEFAULT_RETRY_COUNT, true, + &system_code_response) != PM3_SUCCESS) { PrintAndLogEx(ERR, "Got no response from card"); return PM3_ERFTRANS; } - felica_syscode_response_t rq_syscode_response; - memcpy(&rq_syscode_response, (felica_syscode_response_t *)resp.data.asBytes, sizeof(felica_syscode_response_t)); + if (system_code_response.frame_response.IDm[0] != 0) { + PrintAndLogEx(INFO, "Systems........ " _GREEN_("%u"), system_code_response.number_of_systems[0]); - if (rq_syscode_response.frame_response.IDm[0] != 0) { - PrintAndLogEx(SUCCESS, "Request Response"); - PrintAndLogEx(SUCCESS, "IDm... %s", sprint_hex(rq_syscode_response.frame_response.IDm, sizeof(rq_syscode_response.frame_response.IDm))); - PrintAndLogEx(SUCCESS, " - Number of Systems: %s", sprint_hex(rq_syscode_response.number_of_systems, sizeof(rq_syscode_response.number_of_systems))); - PrintAndLogEx(SUCCESS, " - System Codes: enumerated in ascending order starting from System 0."); - - for (int i = 0; i < rq_syscode_response.number_of_systems[0]; i++) { - PrintAndLogEx(SUCCESS, " - %s", sprint_hex(rq_syscode_response.system_code_list + i * 2, 2)); + 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)); } } diff --git a/include/iso18.h b/include/iso18.h index 3ca4d04b5..e649d4729 100644 --- a/include/iso18.h +++ b/include/iso18.h @@ -175,6 +175,12 @@ typedef struct { felica_status_flags_t status_flags; } PACKED felica_status_response_t; +typedef struct { + uint8_t length[1]; + uint8_t command_code[1]; + uint8_t IDm[8]; +} PACKED felica_request_system_code_request_t; + typedef struct { felica_frame_response_t frame_response; uint8_t number_of_systems[1]; From 36a5c881d02df507141e2d1d7e446ca2bc1ee372 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 11 Apr 2026 08:01:48 +0700 Subject: [PATCH 3/5] proper fix for ip, socket handling for win32 , proxspace env --- client/Makefile | 2 + client/src/cmdlf.c | 165 +++++++++++-------------------- client/src/relay/relay.h | 62 ++++++++++++ client/src/relay/relay_posix.c | 148 ++++++++++++++++++++++++++++ client/src/relay/relay_win32.c | 175 +++++++++++++++++++++++++++++++++ 5 files changed, 445 insertions(+), 107 deletions(-) create mode 100644 client/src/relay/relay.h create mode 100644 client/src/relay/relay_posix.c create mode 100644 client/src/relay/relay_win32.c diff --git a/client/Makefile b/client/Makefile index 51a957c30..308fe2de9 100644 --- a/client/Makefile +++ b/client/Makefile @@ -831,6 +831,8 @@ SRCS = mifare/aiddesfire.c \ pm3line.c \ proxmark3.c \ scandir.c \ + relay/relay_posix.c \ + relay/relay_win32.c \ uart/ringbuffer.c \ uart/uart_common.c \ uart/uart_posix.c \ diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index e8499863a..c21a56318 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -24,17 +24,7 @@ #include #include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#else -#include -#include -#include -#include -#endif +#include "relay/relay.h" #include "cmdparser.h" // command_t #include "comms.h" @@ -1722,33 +1712,9 @@ static int check_autocorrelate(const char *prefix, int clock) { static int lf_relay_tag(uint64_t samples, uint16_t port) { - int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) { - PrintAndLogEx(ERR, "Failed to create socket"); - return PM3_EFAILED; - } - - struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(port), .sin_addr.s_addr = INADDR_ANY }; - int opt = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - PrintAndLogEx(ERR, "Failed to bind to port " _RED_("%u"), port); - close(sock); - return PM3_EFAILED; - } - - if (listen(sock, 1) < 0) { - PrintAndLogEx(ERR, "Failed to listen on socket"); - close(sock); - return PM3_EFAILED; - } - - PrintAndLogEx(INFO, "Relay listening on port " _YELLOW_("%u") "...", port); - - int client = accept(sock, NULL, NULL); - if (client < 0) { - PrintAndLogEx(ERR, "Failed to accept connection"); - close(sock); + relay_socket_t listen_sock = RELAY_SOCKET_INVALID; + relay_socket_t client = relay_listen_accept(port, &listen_sock); + if (client == RELAY_SOCKET_INVALID) { return PM3_EFAILED; } @@ -1763,49 +1729,32 @@ static int lf_relay_tag(uint64_t samples, uint16_t port) { lf_read_internal(false, false, samples); - if (g_GraphTraceLen > 1000 && !getSignalProperties()->isnoise) { + if ((g_GraphTraceLen > 1000) && (getSignalProperties()->isnoise == false)) { PrintAndLogEx(INFO, "Tag detected! Sending %zu samples to Client...", g_GraphTraceLen); - + uint32_t len = (uint32_t)g_GraphTraceLen; - if (send(client, &len, sizeof(len), 0) < 0) { + if (relay_send_all(client, &len, sizeof(len)) != 0) { break; } - - if (send(client, g_GraphBuffer, len * sizeof(int32_t), 0) < 0) { + + if (relay_send_all(client, g_GraphBuffer, len * sizeof(int32_t)) != 0) { break; } msleep(500); } - } - close(client); - close(sock); + + relay_close(client); + relay_close(listen_sock); return PM3_SUCCESS; } static int lf_relay_rdr(const char *ip, uint16_t port) { - int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) { - PrintAndLogEx(ERR, "Failed to create socket"); - return PM3_EFAILED; - } - - struct sockaddr_in addr = {0}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - - if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) { - PrintAndLogEx(ERR, "Invalid IP address... %s:%u", ip, port); - close(sock); - return PM3_EFAILED; - } - - if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - PrintAndLogEx(ERR, "Connection error to %s:%u", ip, port); - close(sock); + relay_socket_t sock = relay_connect(ip, port); + if (sock == RELAY_SOCKET_INVALID) { return PM3_EFAILED; } @@ -1814,9 +1763,9 @@ static int lf_relay_rdr(const char *ip, uint16_t port) { PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); PrintAndLogEx(NORMAL, ""); - int n = 0; - do { - + bool running = true; + while (running) { + if (kbd_enter_pressed()) { SendCommandNG(CMD_BREAK_LOOP, NULL, 0); PrintAndLogEx(DEBUG, "\naborted via keyboard!"); @@ -1825,48 +1774,50 @@ static int lf_relay_rdr(const char *ip, uint16_t port) { } uint32_t incoming_len = 0; - - n = recv(sock, &incoming_len, sizeof(incoming_len), MSG_WAITALL); - - if (n > 0 && incoming_len > 0) { - - if (incoming_len > MAX_GRAPH_TRACE_LEN) { - PrintAndLogEx(ERR, "Received length " _RED_("%u") " exceeds buffer size %u, dropping", incoming_len, MAX_GRAPH_TRACE_LEN); - break; - } - - PrintAndLogEx(INFO, "Received " _YELLOW_("%u") " samples. Processing...", incoming_len); - ssize_t rx = recv(sock, g_GraphBuffer, incoming_len * sizeof(int32_t), MSG_WAITALL); - - if (rx != (ssize_t)(incoming_len * sizeof(int32_t))) { - PrintAndLogEx(ERR, "Short read: expected %u bytes, got %zd", incoming_len * (uint32_t)sizeof(int32_t), rx); - break; - } - - // if previous simulation running, we need to break it. - SendCommandNG(CMD_BREAK_LOOP, NULL, 0); - msleep(300); - - g_GraphTraceLen = incoming_len; - lf_chk_bitstream(); - lfsim_upload_gb(); - struct { - uint16_t len; - uint16_t gap; - } PACKED payload; - payload.len = (g_GraphTraceLen > UINT16_MAX) ? UINT16_MAX : (uint16_t)g_GraphTraceLen; - payload.gap = 0; - - clearCommandBuffer(); - SendCommandNG(CMD_LF_SIMULATE, (uint8_t *)&payload, sizeof(payload)); - - PrintAndLogEx(SUCCESS, "Simulation active."); + int n = relay_recv_all(sock, &incoming_len, sizeof(incoming_len)); + if (n < 0) { + break; } - } while (n > 0); + if (incoming_len == 0) { + continue; + } - close(sock); - + if (incoming_len > MAX_GRAPH_TRACE_LEN) { + PrintAndLogEx(ERR, "Received length " _RED_("%u") " exceeds buffer size %u, dropping", incoming_len, (uint32_t)MAX_GRAPH_TRACE_LEN); + break; + } + + PrintAndLogEx(INFO, "Received " _YELLOW_("%u") " samples. Processing...", incoming_len); + + int rx = relay_recv_all(sock, g_GraphBuffer, incoming_len * sizeof(int32_t)); + if (rx < 0) { + PrintAndLogEx(ERR, "Short read receiving sample data"); + break; + } + + // if previous simulation running, we need to break it. + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + msleep(300); + + g_GraphTraceLen = incoming_len; + lf_chk_bitstream(); + lfsim_upload_gb(); + + struct { + uint16_t len; + uint16_t gap; + } PACKED payload; + payload.len = (g_GraphTraceLen > UINT16_MAX) ? UINT16_MAX : (uint16_t)g_GraphTraceLen; + payload.gap = 0; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_SIMULATE, (uint8_t *)&payload, sizeof(payload)); + + PrintAndLogEx(SUCCESS, "Simulation active."); + } + + relay_close(sock); return PM3_SUCCESS; } diff --git a/client/src/relay/relay.h b/client/src/relay/relay.h new file mode 100644 index 000000000..d5397e5b1 --- /dev/null +++ b/client/src/relay/relay.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// TCP relay socket abstraction layer +//----------------------------------------------------------------------------- + +#ifndef RELAY_H__ +#define RELAY_H__ + +#include "common.h" + +#ifdef _WIN32 +#include +typedef SOCKET relay_socket_t; +#define RELAY_SOCKET_INVALID INVALID_SOCKET +#else +typedef int relay_socket_t; +#define RELAY_SOCKET_INVALID (-1) +#endif + +// Initialize relay subsystem (call once at startup). +// On Win32 this calls WSAStartup; on POSIX it is a no-op. +int relay_init(void); + +// Tear down relay subsystem (call once at shutdown). +// On Win32 this calls WSACleanup; on POSIX it is a no-op. +void relay_cleanup(void); + +// Create a TCP server socket bound to INADDR_ANY:, listen, and +// block until one client connects. Returns the *client* fd/SOCKET. +// On error, returns RELAY_SOCKET_INVALID. Caller owns both sockets; +// the listening socket is written to *listen_sock so it can be closed. +relay_socket_t relay_listen_accept(uint16_t port, relay_socket_t *listen_sock); + +// Connect to a TCP server at ip:port. +// Returns the connected socket or RELAY_SOCKET_INVALID on error. +relay_socket_t relay_connect(const char *ip, uint16_t port); + +// Send exactly `len` bytes from `buf`. +// Returns 0 on success, -1 on error. +int relay_send_all(relay_socket_t sock, const void *buf, uint32_t len); + +// Receive exactly `len` bytes into `buf`. +// Returns number of bytes received, or -1 on error / disconnect. +int relay_recv_all(relay_socket_t sock, void *buf, uint32_t len); + +// Close a relay socket. +void relay_close(relay_socket_t sock); + +#endif // RELAY_H__ diff --git a/client/src/relay/relay_posix.c b/client/src/relay/relay_posix.c new file mode 100644 index 000000000..e276d1cfc --- /dev/null +++ b/client/src/relay/relay_posix.c @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// TCP relay socket abstraction — POSIX implementation +//----------------------------------------------------------------------------- + +#ifndef _WIN32 + +#include "relay.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ui.h" + +int relay_init(void) { + return PM3_SUCCESS; +} + +void relay_cleanup(void) { +} + +relay_socket_t relay_listen_accept(uint16_t port, relay_socket_t *listen_sock) { + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + PrintAndLogEx(ERR, "Failed to create socket (%s)", strerror(errno)); + return RELAY_SOCKET_INVALID; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr.s_addr = INADDR_ANY + }; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + PrintAndLogEx(ERR, "Failed to bind to port " _RED_("%u") " (%s)", port, strerror(errno)); + close(sock); + return RELAY_SOCKET_INVALID; + } + + if (listen(sock, 1) < 0) { + PrintAndLogEx(ERR, "Failed to listen on socket (%s)", strerror(errno)); + close(sock); + return RELAY_SOCKET_INVALID; + } + + PrintAndLogEx(INFO, "Relay listening on port " _YELLOW_("%u") "...", port); + + int client = accept(sock, NULL, NULL); + if (client < 0) { + PrintAndLogEx(ERR, "Failed to accept connection (%s)", strerror(errno)); + close(sock); + return RELAY_SOCKET_INVALID; + } + + if (listen_sock != NULL) { + *listen_sock = sock; + } + + return client; +} + +relay_socket_t relay_connect(const char *ip, uint16_t port) { + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + PrintAndLogEx(ERR, "Failed to create socket (%s)", strerror(errno)); + return RELAY_SOCKET_INVALID; + } + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) { + PrintAndLogEx(ERR, "Invalid IP address... %s:%u", ip, port); + close(sock); + return RELAY_SOCKET_INVALID; + } + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + PrintAndLogEx(ERR, "Connection error to %s:%u (%s)", ip, port, strerror(errno)); + close(sock); + return RELAY_SOCKET_INVALID; + } + + return sock; +} + +int relay_send_all(relay_socket_t sock, const void *buf, uint32_t len) { + const uint8_t *p = (const uint8_t *)buf; + uint32_t remaining = len; + + while (remaining > 0) { + ssize_t n = send(sock, p, remaining, 0); + if (n <= 0) { + return -1; + } + p += n; + remaining -= (uint32_t)n; + } + return 0; +} + +int relay_recv_all(relay_socket_t sock, void *buf, uint32_t len) { + uint8_t *p = (uint8_t *)buf; + uint32_t remaining = len; + + while (remaining > 0) { + ssize_t n = recv(sock, p, remaining, 0); + if (n <= 0) { + return -1; + } + p += n; + remaining -= (uint32_t)n; + } + return (int)len; +} + +void relay_close(relay_socket_t sock) { + if (sock != RELAY_SOCKET_INVALID) { + close(sock); + } +} + +#endif // !_WIN32 diff --git a/client/src/relay/relay_win32.c b/client/src/relay/relay_win32.c new file mode 100644 index 000000000..aba106e7a --- /dev/null +++ b/client/src/relay/relay_win32.c @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// TCP relay socket abstraction — Win32 implementation +//----------------------------------------------------------------------------- + +#ifdef _WIN32 + +#include "relay.h" + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include "ui.h" + +static bool g_wsa_initialized = false; + +int relay_init(void) { + if (g_wsa_initialized) { + return PM3_SUCCESS; + } + + WSADATA wsa; + int ret = WSAStartup(MAKEWORD(2, 2), &wsa); + if (ret != 0) { + PrintAndLogEx(ERR, "WSAStartup failed with error %d", ret); + return PM3_EFAILED; + } + + g_wsa_initialized = true; + return PM3_SUCCESS; +} + +void relay_cleanup(void) { + if (g_wsa_initialized) { + WSACleanup(); + g_wsa_initialized = false; + } +} + +relay_socket_t relay_listen_accept(uint16_t port, relay_socket_t *listen_sock) { + + if (relay_init() != PM3_SUCCESS) { + return RELAY_SOCKET_INVALID; + } + + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET) { + PrintAndLogEx(ERR, "Failed to create socket (WSA %d)", WSAGetLastError()); + return RELAY_SOCKET_INVALID; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { + PrintAndLogEx(ERR, "Failed to bind to port " _RED_("%u") " (WSA %d)", port, WSAGetLastError()); + closesocket(sock); + return RELAY_SOCKET_INVALID; + } + + if (listen(sock, 1) == SOCKET_ERROR) { + PrintAndLogEx(ERR, "Failed to listen on socket (WSA %d)", WSAGetLastError()); + closesocket(sock); + return RELAY_SOCKET_INVALID; + } + + PrintAndLogEx(INFO, "Relay listening on port " _YELLOW_("%u") "...", port); + + SOCKET client = accept(sock, NULL, NULL); + if (client == INVALID_SOCKET) { + PrintAndLogEx(ERR, "Failed to accept connection (WSA %d)", WSAGetLastError()); + closesocket(sock); + return RELAY_SOCKET_INVALID; + } + + if (listen_sock != NULL) { + *listen_sock = sock; + } + + return client; +} + +relay_socket_t relay_connect(const char *ip, uint16_t port) { + + if (relay_init() != PM3_SUCCESS) { + return RELAY_SOCKET_INVALID; + } + + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET) { + PrintAndLogEx(ERR, "Failed to create socket (WSA %d)", WSAGetLastError()); + return RELAY_SOCKET_INVALID; + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) { + PrintAndLogEx(ERR, "Invalid IP address... %s:%u", ip, port); + closesocket(sock); + return RELAY_SOCKET_INVALID; + } + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { + PrintAndLogEx(ERR, "Connection error to %s:%u (WSA %d)", ip, port, WSAGetLastError()); + closesocket(sock); + return RELAY_SOCKET_INVALID; + } + + return sock; +} + +int relay_send_all(relay_socket_t sock, const void *buf, uint32_t len) { + const char *p = (const char *)buf; + uint32_t remaining = len; + + while (remaining > 0) { + int n = send(sock, p, (int)remaining, 0); + if (n == SOCKET_ERROR || n <= 0) { + return -1; + } + p += n; + remaining -= (uint32_t)n; + } + return 0; +} + +int relay_recv_all(relay_socket_t sock, void *buf, uint32_t len) { + char *p = (char *)buf; + uint32_t remaining = len; + + while (remaining > 0) { + int n = recv(sock, p, (int)remaining, 0); + if (n == SOCKET_ERROR || n <= 0) { + return -1; + } + p += n; + remaining -= (uint32_t)n; + } + return (int)len; +} + +void relay_close(relay_socket_t sock) { + if (sock != INVALID_SOCKET) { + closesocket(sock); + } +} + +#endif // _WIN32 From c502d2d88d3f271e94c0f8831c91a7b1292bfeda Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 11 Apr 2026 08:06:48 +0700 Subject: [PATCH 4/5] fix cmake --- client/CMakeLists.txt | 2 ++ client/experimental_lib/CMakeLists.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 732636089..bcbfefd42 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -362,6 +362,8 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/desfiretest.c ${PM3_ROOT}/client/src/mifare/gallaghercore.c ${PM3_ROOT}/client/src/mifare/gallaghertest.c + ${PM3_ROOT}/client/src/relay/relay_posix.c + ${PM3_ROOT}/client/src/relay/relay_win32.c ${PM3_ROOT}/client/src/uart/ringbuffer.c ${PM3_ROOT}/client/src/uart/uart_common.c ${PM3_ROOT}/client/src/uart/uart_posix.c diff --git a/client/experimental_lib/CMakeLists.txt b/client/experimental_lib/CMakeLists.txt index 885f6b2b2..390929f6f 100644 --- a/client/experimental_lib/CMakeLists.txt +++ b/client/experimental_lib/CMakeLists.txt @@ -282,6 +282,8 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/mifare/desfiretest.c ${PM3_ROOT}/client/src/mifare/gallaghercore.c ${PM3_ROOT}/client/src/mifare/gallaghertest.c + ${PM3_ROOT}/client/src/relay/relay_posix.c + ${PM3_ROOT}/client/src/relay/relay_win32.c ${PM3_ROOT}/client/src/uart/ringbuffer.c ${PM3_ROOT}/client/src/uart/uart_common.c ${PM3_ROOT}/client/src/uart/uart_posix.c From 6af1a8e4325830dfb626a7edfef4b0bdc4f8a513 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 11 Apr 2026 09:04:55 +0700 Subject: [PATCH 5/5] updated help text for `lf realy --- client/src/cmdlf.c | 25 +++-- client/src/pm3line_vocabulary.h | 6 +- doc/commands.json | 186 +++++++++++++++++++++++++++++--- doc/commands.md | 6 ++ 4 files changed, 194 insertions(+), 29 deletions(-) diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index c21a56318..5ebdf31d4 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -1824,19 +1824,24 @@ static int lf_relay_rdr(const char *ip, uint16_t port) { int CmdLFRelay(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf relay", - "Relay LF signal between two Proxmark3 devices over TCP.\n" - "By default it uses PORT 8000.\n" - "One device acts as a Tag Proxy, the other as a Reader Client.", - "lf relay --tag -s 7000\n" - "lf relay --rdr --ip 192.168.1.141" - ); + "Relay LF signal between two Proxmark3 devices over TCP.\n" + "By default it uses PORT 8000 and uses 40000 samples from Graphbuffer\n" + " --rdr : Reading device, act as IP client and reads LF tag and sends data\n" + " --tag : Simulation device, act as IP server and simulates relayed data\n", + _WHITE_("Device A, reading LF tag, client") "\n" + "lf relay --rdr --ip 192.168.1.141 -> Client, connect to IP 192.168.1.141:8000\n" + "lf relay --rdr --ip 192.168.1.141 -p 18111 -> Client, connect to IP 192.168.1.141:18111 \n\n" + _WHITE_("Device B, simulate LF tag, server") "\n" + "lf relay --tag -p 8111 -> Server listening port 8111, recv 40000 samples\n" + "lf relay --tag -s 10000 -> Server listening port 8000, recv 10000 samples\n" + ); void *argtable[] = { arg_param_begin, - arg_lit0(NULL, "tag", "Act as Tag Proxy (Server)"), - arg_lit0(NULL, "rdr", "Act as Reader Client (Connects to Proxy)"), - arg_str0("i", "ip", "", "Target IP address for Reader mode"), - arg_u64_0("s", "samples", "", "Number of samples to collect (default 40000)"), + arg_lit0(NULL, "tag", "Simulation device, act as Server"), + arg_lit0(NULL, "rdr", "Sniffing device, act as client"), + arg_str0("i", "ip", "", "Target IPv4 address to send data to. Used with `--rdr`"), + arg_u64_0("s", "samples", "", "Number of samples to collect (def: 40000)"), arg_u64_0("p", "port", "", "Port number (def: 8000)"), arg_param_end }; diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 307a65e0d..be7ee4591 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -293,6 +293,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf gst info" }, { 0, "hf gst read" }, { 1, "hf secc help" }, + { 0, "hf secc info" }, { 0, "hf secc sim" }, { 0, "hf secc sniff" }, { 1, "hf iclass help" }, @@ -435,6 +436,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mf encodehid" }, { 1, "hf mfp help" }, { 1, "hf mfp list" }, + { 1, "hf mfp acl" }, { 0, "hf mfp auth" }, { 0, "hf mfp chk" }, { 0, "hf mfp dump" }, @@ -620,6 +622,7 @@ const static vocabulary_t vocabulary[] = { { 0, "lf config" }, { 0, "lf cmdread" }, { 0, "lf read" }, + { 0, "lf relay" }, { 1, "lf search" }, { 0, "lf sim" }, { 0, "lf simask" }, @@ -1024,10 +1027,10 @@ const static vocabulary_t vocabulary[] = { { 0, "script run hf_ntag_bruteforce.lua" }, { 0, "script run hf_ntag_dt.lua" }, { 0, "script run init_rdv4.lua" }, - { 0, "script run kybercrystals.lua" }, { 0, "script run lf_awid_bulkclone.lua" }, { 0, "script run lf_electra.lua" }, { 0, "script run lf_em4100_bulk.lua" }, + { 0, "script run lf_em4x05_kybercrystals.lua" }, { 0, "script run lf_em_tearoff.lua" }, { 0, "script run lf_em_tearoff_protect.lua" }, { 0, "script run lf_hid_bulkclone.lua" }, @@ -1046,7 +1049,6 @@ const static vocabulary_t vocabulary[] = { { 0, "script run ntag_clean.lua" }, { 0, "script run ntag_getsig.lua" }, { 0, "script run ntag_hammertime.lua" }, - { 0, "script run paxton_clone.lua" }, { 0, "script run data_tracetest.lua" }, { 0, "script run hf_read.lua" }, { 0, "script run lf_t55xx_defaultask.lua" }, diff --git a/doc/commands.json b/doc/commands.json index 111422cb0..1d338755c 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3523,7 +3523,7 @@ "--ki Key index to select key from memory 'hf iclass managekeys'", "--credit key is assumed to be the credit key", "-s tearoff delay start (in us) must be between 1 and 43000 (43ms). Precision is about 1/3 us", - "-i tearoff delay increment (in us) - default 10", + "-i tearoff delay increment (in us) - default 5", "-e tearoff delay end (in us) must be a higher value than the start delay", "-o, --otp Custom OTP value as 2 hex bytes", "--dns Do not stabilize the bits, and return the raw dump of the block after tearoff", @@ -3805,7 +3805,7 @@ }, "hf iclass legbrute": { "command": "hf iclass legbrute", - "description": "This command takes sniffed trace data and a partial raw key and bruteforces the remaining 40 bits of the raw key. Complete 40 bit keyspace is 1'099'511'627'776 and command is locked down to max 16 threads currently. A possible worst case scenario on 16 threads estimates XXX days YYY hours MMM minutes.", + "description": "This command takes sniffed trace data and a partial raw key and bruteforces the remaining 40 bits of the raw key. Complete 40 bit keyspace is 1'099'511'627'776.", "notes": [ "hf iclass legbrute --epurse feffffffffffffff --macs1 1306cad9b6c24466 --macs2 f0bf905e35f97923 --pk B4F12AADC5301225" ], @@ -3817,13 +3817,14 @@ "--macs2 MACs captured from the reader, different than the first set (with the same csn and epurse value)", "--pk Partial Key from legrec or starting key of keyblock from legbrute", "--index Where to start from to retrieve the key, default 0 - value in millions e.g. 1 is 1 million", - "--threads Number of threads to use, by default it uses the cpu's max threads (max 16)." + "--threads Number of threads to use, by default it uses the cpu's max threads.", + "--dbg Print first 2 key candidates and midpoint per thread, then exit (use to verify thread partitioning)" ], - "usage": "hf iclass legbrute [-h] --epurse --macs1 --macs2 --pk [--index ] [--threads ]" + "usage": "hf iclass legbrute [-h] --epurse --macs1 --macs2 --pk [--index ] [--threads ] [--dbg]" }, "hf iclass legrec": { "command": "hf iclass legrec", - "description": "Attempts to recover the diversified key of a specific iCLASS card. This may take several days. The card must remain be on the PM3 antenna during the whole process. ! Warning ! This process may brick the card! ! Warning !", + "description": "Attempts to recover the diversified key of a specific iCLASS card. This may take several days. The card must remain on the PM3 antenna during the whole process. ! Warning ! This process may brick the card! ! Warning !", "notes": [ "hf iclass legrec --macs 0000000089cb984b", "hf iclass legrec --macs 0000000089cb984b --index 0 --loop 100 --notest" @@ -3839,10 +3840,9 @@ "--allnight Loops the loop for 10 times, recommended loop value of 5000", "--fast Increases the speed (4.6->7.4 key updates/second), higher risk to brick the card", "--sl Lower card comms delay times, further speeds increases, may cause more errors", - "--est Estimates the key updates based on the card's CSN assuming standard key, can be used with --credit option", - "--credit EXPERIMENTAL : Recover the credit key using KD 0" + "--est Estimates the key updates based on the card's CSN assuming standard key, can be used with --credit option" ], - "usage": "hf iclass legrec [-h] --macs [--index ] [--loop ] [--debug] [--notest] [--allnight] [--fast] [--sl] [--est] [--credit]" + "usage": "hf iclass legrec [-h] [--macs ] [--index ] [--loop ] [--debug] [--notest] [--allnight] [--fast] [--sl] [--est]" }, "hf iclass loclass": { "command": "hf iclass loclass", @@ -6045,6 +6045,35 @@ ], "usage": "hf mfdes bruteaid [-h] [--start ] [--end ] [-i ] [--preset ]" }, + "hf mfdes brutedamslot": { + "command": "hf mfdes brutedamslot", + "description": "Recover DAM slot to delegated AID mappings by bruteforce. WARNING: This command takes a loooong time", + "notes": [ + "hf mfdes brutedamslot -> bruteforce all DAM slots", + "hf mfdes brutedamslot --start 0000 --end 00ff -> bruteforce specific DAM slot range", + "hf mfdes brutedamslot --step 16 -> bruteforce DAM slots with step 16", + "hf mfdes brutedamslot --no-auth -> execute without authentication" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses", + "-v, --verbose Verbose output", + "-n, --keyno Key number (default: 0 / PICC key)", + "-t, --algo Crypt algo", + "-k, --key Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)", + "--kdf Key Derivation Function (KDF)", + "-i, --kdfi KDF input (1-31 hex bytes)", + "-m, --cmode Communicaton mode", + "-c, --ccset Communicaton command set", + "--schann Secure channel", + "--start Starting DAM slot (2 hex bytes, little endian on card)", + "--end Last DAM slot (2 hex bytes, little endian on card)", + "--step Increment step when bruteforcing DAM slots", + "--no-auth Execute without authentication" + ], + "usage": "hf mfdes brutedamslot [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--start ] [--end ] [--step ] [--no-auth]" + }, "hf mfdes bruteisofid": { "command": "hf mfdes bruteisofid", "description": "Recover ISO file IDs by bruteforce. WARNING: This command takes a loooong time", @@ -6281,6 +6310,49 @@ ], "usage": "hf mfdes createapp [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--rawdata ] [--aid ] [--fid ] [--dfname ] [--dfhex ] [--ks1 ] [--ks2 ] [--dstalgo ] [--numkeys ] [--no-auth]" }, + "hf mfdes createdelegateapp": { + "command": "hf mfdes createdelegateapp", + "description": "Create delegated application (CreateDelegatedApplication / 0xC9). Master key needs to be provided.", + "notes": [ + "Command is built from fields and sends two frames: C9 + AF continuation.", + "Authentication is always performed with DAM key number 0x10.", + "EncK and DAMMAC are calculated from supplied key material.", + "", + "Structured mode examples:", + "hf mfdes createdelegateapp --aid 123456 --damslot 0001 --damslotver 00 --quota 0010 --ks1 0F --ks2 AE --algo 2TDEA --key 00000000000000000000000000000000 --damenckey 00112233445566778899AABBCCDDEEFF --dammackey 8899AABBCCDDEEFF0011223344556677 --dstkey 00112233445566778899AABBCCDDEEFF --dstkeyver 00", + "hf mfdes createdelegateapp --aid 123456 --damslot 0001 --quota 0010 --ks1 0F --dstalgo aes --numkeys 14 --ks3 01 --fid E110 --dfname D2760000850101 --algo 2TDEA --key 00000000000000000000000000000000 --damenckey 00112233445566778899AABBCCDDEEFF --dammackey 8899AABBCCDDEEFF0011223344556677 --dstkey 00112233445566778899AABBCCDDEEFF --dstkeyver 00" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses", + "-v, --verbose Verbose output", + "-t, --algo Crypt algo", + "-k, --key Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)", + "--kdf Key Derivation Function (KDF)", + "-i, --kdfi KDF input (1-31 hex bytes)", + "-m, --cmode Communicaton mode", + "-c, --ccset Communicaton command set", + "--schann Secure channel", + "--aid Application ID for create. Mandatory in structured mode. (3 hex bytes, big endian)", + "--damslot DAM slot number (2 hex bytes, little endian on card)", + "--damslotver DAM slot version (1 hex byte, def: 00)", + "--quota Quota in blocks (2 hex bytes, little endian on card, def: 0000)", + "--ks1 Key settings 1 (1 hex byte, def: 0x0F)", + "--ks2 Key settings 2 (1 hex byte, def: 0x0E)", + "--ks3 Key settings 3 (1 hex byte, optional)", + "--fid ISO file ID (2 hex bytes, big endian), optional", + "--dfname ISO DF Name (1..16 bytes, hex), optional", + "--dstalgo Application key crypt algo (used when ks2 omitted, def: DES)", + "--numkeys Number of keys 0x01..0x0e (used when ks2 omitted, def: 0x01)", + "--damenckey DAM ENC key (16 bytes for AES/2TDEA, 24 bytes for 3TDEA)", + "--dammackey DAM MAC key (16 bytes for AES/2TDEA, 24 bytes for 3TDEA)", + "--dstkey Initial delegated-app key (16 bytes for 2TDEA/AES, 24 bytes for 3TDEA)", + "--dstkeyver Initial delegated-app key version (1 hex byte, def: 00)", + "--no-auth Execute without authentication" + ], + "usage": "hf mfdes createdelegateapp [-hav] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--aid ] [--damslot ] [--damslotver ] [--quota ] [--ks1 ] [--ks2 ] [--ks3 ] [--fid ] [--dfname ] [--dstalgo ] [--numkeys ] [--damenckey ] [--dammackey ] [--dstkey ] [--dstkeyver ] [--no-auth]" + }, "hf mfdes createfile": { "command": "hf mfdes createfile", "description": "Create Standard/Backup file in the application. Application master key needs to be provided or flag --no-auth set (depend on application settings).", @@ -6664,6 +6736,33 @@ ], "usage": "hf mfdes getappnames [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--no-auth]" }, + "hf mfdes getdelegateappinfo": { + "command": "hf mfdes getdelegateappinfo", + "description": "Get delegated application information for DAM slot (GetDelegatedInfo / 0x69).", + "notes": [ + "By default authentication is performed with PICC key number 0x00.", + "Use --keyno to pick another key number, or --no-auth to skip authentication.", + "hf mfdes getdelegateappinfo --damslot 0001 --algo 2TDEA --key 00000000000000000000000000000000", + "hf mfdes getdelegateappinfo --damslot 0001 --no-auth" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-a, --apdu Show APDU requests and responses", + "-v, --verbose Verbose output", + "-n, --keyno Key number (default: 0 / PICC key)", + "-t, --algo Crypt algo", + "-k, --key Key for authenticate (HEX 8(DES), 16(2TDEA or AES) or 24(3TDEA) bytes)", + "--kdf Key Derivation Function (KDF)", + "-i, --kdfi KDF input (1-31 hex bytes)", + "-m, --cmode Communicaton mode", + "-c, --ccset Communicaton command set", + "--schann Secure channel", + "--damslot DAM slot number (2 hex bytes, little endian on card)", + "--no-auth Execute without authentication" + ], + "usage": "hf mfdes getdelegateappinfo [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--damslot ] [--no-auth]" + }, "hf mfdes getfileids": { "command": "hf mfdes getfileids", "description": "Get File IDs list from card. Master key needs to be provided or flag --no-auth set.", @@ -7185,6 +7284,20 @@ ], "usage": "hf mfdes write [-hav] [-n ] [-t ] [-k ] [--kdf ] [-i ] [-m ] [-c ] [--schann ] [--aid ] [--fid ] [--no-auth] [--type ] [-o ] [-d ] [--debit] [--commit] [--updaterec ] [--isoid ] [--fileisoid ] [--readerid ] [--trkey ]" }, + "hf mfp acl": { + "command": "hf mfp acl", + "description": "Print decoded MIFARE Plus access rights (ACL), A = key A B = key B AB = both key A and B ACCESS = access bytes inside sector trailer block Increment, decrement, transfer, restore is for value blocks", + "notes": [ + "hf mf acl", + "hf mf acl -d FF0780" + ], + "offline": true, + "options": [ + "-h, --help This help", + "-d, --data ACL bytes specified as 4 hex bytes" + ], + "usage": "hf mfp acl [-h] -d " + }, "hf mfp auth": { "command": "hf mfp auth", "description": "Executes AES authentication command for MIFARE Plus card", @@ -7311,7 +7424,7 @@ }, "hf mfp help": { "command": "hf mfp help", - "description": "help This help list List MIFARE Plus history --------------------------------------------------------------------------------------- hf mfp list available offline: yes Alias of `trace list -t mfp -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List MIFARE Plus history acl Decode ACL values for Mifare Plus --------------------------------------------------------------------------------------- hf mfp list available offline: yes Alias of `trace list -t mfp -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "hf mfp list --frame -> show frame delay times", "hf mfp list -1 -> use trace buffer" @@ -8320,12 +8433,24 @@ }, "hf secc help": { "command": "hf secc help", - "description": "-------- ----------- HID Config Card ----------- help This help --------------------------------------------------------------------------------------- hf secc sim available offline: no Simulate a HID iCLASS SE Config Card (JCOP / GlobalPlatform SCP02). Responds to SELECT AID (0013/0017), A0 D4, INITIALIZE UPDATE, and EXTERNAL AUTH. Load card parameters (UID, AID, SCP02Key) from a JSON file.", + "description": "-------- ----------- HID Config Card ----------- help This help --------------------------------------------------------------------------------------- hf secc info available offline: no Read and decode Card Recognition Data from a GlobalPlatform card. Sends GET DATA (80 CA 00 66 00) and parses the Card Recognition Template (tag 73) to identify platform, SCP type, and chip family.", + "notes": [ + "hf secc info" + ], + "offline": true, + "options": [ + "-h, --help This help" + ], + "usage": "hf secc info [-h]" + }, + "hf secc sim": { + "command": "hf secc sim", + "description": "Simulate a HID iCLASS SE Config Card (JCOP / GlobalPlatform SCP02). APDUs are matched against the JSON APDUResponses table; INITIALIZE UPDATE and EXTERNAL AUTH are handled by the built-in SCP02 crypto. Anything else falls through to the JSON DefaultResponse (or 9000 if none is set).", "notes": [ "hf secc sim -f hidconfig_sample", "hf secc sim -f hidconfig_sample -n 5 -> stop after 5 reader interactions" ], - "offline": true, + "offline": false, "options": [ "-h, --help This help", "-f, --file JSON file with UID, AID, SCP02Key (without .json extension)", @@ -8335,11 +8460,13 @@ }, "hf secc sniff": { "command": "hf secc sniff", - "description": "Sniff the communication between a HID Config Card reader and card. Use `hf 14a list` to view collected data.", + "description": "Sniff the communication between a HID Config Card reader and card. Use `hf seos list` to view collected data. With -j and no -d, jams responses to APDU A0 D4 00 00 00. With -j -d jams responses to the specified APDU. Use -r to override the jam response payload (default: 00009000).", "notes": [ "hf secc sniff", "hf secc sniff -j -> jam A0 D4 00 00 00, respond 00 00 90 00", - "hf secc sniff -c -r -> trigger on card or reader data" + "hf secc sniff -j -d A0D4000000 -> same, APDU specified explicitly", + "hf secc sniff -j -d A0D4000000 -r 9000 -> jam A0D4000000, respond 90 00", + "hf secc sniff -c -i -> trigger on card data, interactive" ], "offline": false, "options": [ @@ -8347,9 +8474,11 @@ "-c, --card triggered by first data from card", "-r, --reader triggered by first 7-bit request from reader (REQ, WUP)", "-i, --interactive console will not be returned until sniff finishes or is aborted", - "-j, --jam jam APDU A0 D4 00 00 00, respond with 00 00 90 00" + "-j, --jam jam responses to a specific APDU (see -d/-a)", + "-d, --apdu APDU bytes to jam (default: A0D4000000)", + "-a, --resp response payload when jamming (default: 00009000)" ], - "usage": "hf secc sniff [-hcrij]" + "usage": "hf secc sniff [-hcrij] [-d ] [-a ]" }, "hf seos adf": { "command": "hf seos adf", @@ -12092,6 +12221,29 @@ ], "usage": "lf read [-hv@] [-s ]" }, + "lf relay": { + "command": "lf relay", + "description": "Relay LF signal between two Proxmark3 devices over TCP. By default it uses PORT 8000 and uses 40000 samples from Graphbuffer --rdr : Reading device, act as IP client and reads LF tag and sends data --tag : Simulation device, act as IP server and simulates relayed data", + "notes": [ + "Device A, reading LF tag, client", + "lf relay --rdr --ip 192.168.1.141 -> Client, connect to IP 192.168.1.141:8000", + "lf relay --rdr --ip 192.168.1.141 -p 18111 -> Client, connect to IP 192.168.1.141:18111", + "", + "Device B, simulate LF tag, server", + "lf relay --tag -p 8111 -> Server listening port 8111, recv 40000 samples", + "lf relay --tag -s 10000 -> Server listening port 8000, recv 10000 samples" + ], + "offline": false, + "options": [ + "-h, --help This help", + "--tag Simulation device, act as Server", + "--rdr Sniffing device, act as client", + "-i, --ip Target IPv4 address to send data to. Used with `--rdr`", + "-s, --samples Number of samples to collect (def: 40000)", + "-p, --port Port number (def: 8000)" + ], + "usage": "lf relay [-h] [--tag] [--rdr] [-i ] [-s ] [-p ]" + }, "lf search": { "command": "lf search", "description": "Read and search for valid known tag. For offline mode, you can `data load` first then search.", @@ -14286,8 +14438,8 @@ } }, "metadata": { - "commands_extracted": 813, + "commands_extracted": 819, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-04-02T12:42:42" + "extracted_on": "2026-04-11T02:04:09" } } diff --git a/doc/commands.md b/doc/commands.md index b561d7e1a..cfb47754a 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -432,6 +432,7 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- |`hf secc help `|Y |`This help` +|`hf secc info `|N |`Read and decode Card Recognition Data (GP tag 0066)` |`hf secc sim `|N |`Simulate HID iCLASS SE Config Card` |`hf secc sniff `|N |`Sniff reader<->card, jam A0 D4 APDU` @@ -638,6 +639,7 @@ Check column "offline" for their availability. |------- |------- |----------- |`hf mfp help `|Y |`This help` |`hf mfp list `|Y |`List MIFARE Plus history` +|`hf mfp acl `|Y |`Decode ACL values for Mifare Plus` |`hf mfp auth `|N |`Authentication` |`hf mfp chk `|N |`Check keys` |`hf mfp dump `|N |`Dump MIFARE Plus tag to file` @@ -713,7 +715,10 @@ Check column "offline" for their availability. |`hf mfdes getaids `|N |`Get Application IDs list` |`hf mfdes getappnames `|N |`Get Applications list` |`hf mfdes bruteaid `|N |`Recover AIDs by bruteforce` +|`hf mfdes brutedamslot `|N |`Recover DAM slots to delegated AIDs by bruteforce` |`hf mfdes createapp `|N |`Create Application` +|`hf mfdes createdelegateapp`|N |`Create Delegated Application` +|`hf mfdes getdelegateappinfo`|N |`Get Delegated Application info by DAM slot` |`hf mfdes deleteapp `|N |`Delete Application` |`hf mfdes selectapp `|N |`Select Application ID` |`hf mfdes selectisofid `|N |`Select file by ISO ID` @@ -940,6 +945,7 @@ Check column "offline" for their availability. |`lf config `|N |`Get/Set config for LF sampling, bit/sample, decimation, frequency` |`lf cmdread `|N |`Modulate LF reader field to send command before read` |`lf read `|N |`Read LF tag` +|`lf relay `|N |`LF relay between two pm3 devices (tag/rdr mode)` |`lf search `|Y |`Read and Search for valid known tag` |`lf sim `|N |`Simulate LF tag from buffer` |`lf simask `|N |`Simulate ASK tag`