diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd098644..3e50c2dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added `hf felica sim` command (@kormax) - Added `mad read`, `mad write`, `mad verify`, `mad decode`, `mad encode` commands with typed struct MAD API (@AlxCzl) - Added `hf mfdes getversion` command (@kormax) - Fixed iCLASS emulator writes clearing previously loaded emulator memory after an FPGA reload (@cindersocket) diff --git a/armsrc/Makefile b/armsrc/Makefile index 8309cca26..ab7fd3d9c 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -41,7 +41,7 @@ SRC_ISO14443a = iso14443a.c secc.c mifareutil.c mifarecmd.c epa.c mifaresim.c sa #UNUSED: mifaresniff.c SRC_ISO14443b = iso14443b.c -SRC_FELICA = felica.c +SRC_FELICA = felica.c felicasim.c SRC_CRAPTO1 = crypto1.c des.c desfire_crypto.c mifaredesfire.c aes.c platform_util.c SRC_CRC = crc.c crc16.c crc32.c SRC_ICLASS = iclass.c optimized_cipherutils.c optimized_ikeys.c optimized_elite.c optimized_cipher.c sam_picopass.c diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 6f0bcf045..cbc7394dc 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -39,6 +39,7 @@ #include "iso15693.h" #include "thinfilm.h" #include "felica.h" +#include "felicasim.h" #include "hitag2.h" #include "hitag2_crack.h" #include "hitagS.h" @@ -1783,6 +1784,10 @@ static void PacketReceived(PacketCommandNG *packet) { felica_sendraw(packet); break; } + case CMD_HF_FELICA_SIMULATE: { + felicasim_standard(packet); + break; + } case CMD_HF_FELICALITE_SIMULATE: { struct p { uint8_t uid[8]; diff --git a/armsrc/felica.c b/armsrc/felica.c index 6b36ac21d..4dd9c5e13 100644 --- a/armsrc/felica.c +++ b/armsrc/felica.c @@ -26,16 +26,13 @@ #include "ticks.h" #include "iso18.h" -// FeliCa timings -// -// In the ISO18092/FeliCa FPGA bitstream, the SSC clock is the RF bit clock: -// fc/64 at 212 kbit/s and fc/32 at 424 kbit/s. Trace timestamps are stored -// in carrier periods, so convert at the logging boundary. +#define AddCrc(data, len) compute_crc(CRC_FELICA, (data), (len), (data)+(len)+1, (data)+(len)) +static uint8_t felica_select_card(felica_card_select_t *card); + +//structure to hold outgoing NFC frame +static uint8_t frameSpace[FELICA_MAX_RF_FRAME_SIZE]; + #define FELICA_PREAMBLE_BYTES 6U -#define FELICA_BITS_PER_BYTE 8U -#define FELICA_212K_CARRIER_PERIODS_PER_BIT 64U -#define FELICA_424K_CARRIER_PERIODS_PER_BIT 32U -#define FELICA_212K_CARRIER_TO_TIMER_TICKS(x) (((x) + FELICA_212K_CARRIER_PERIODS_PER_BIT - 1U) / FELICA_212K_CARRIER_PERIODS_PER_BIT) // Keep a conservative reader-to-reader guard of one FeliCa polling slot-0 wait // (512 bit periods). The spec minimum of 6800 carrier periods is shorter. @@ -46,23 +43,19 @@ #ifndef FELICA_FRAME_DELAY_TIME # define FELICA_FRAME_DELAY_TIME (FELICA_212K_CARRIER_TO_TIMER_TICKS(2672) + 1) #endif -#ifndef DELAY_AIR2ARM_AS_READER -#define DELAY_AIR2ARM_AS_READER (3 + 16 + 8 + 8*16 + 4*16 - 8*16) // 91 + +//b2 4d is SYNC, 45645 in 16-bit notation, 10110010 01001101 binary. Frame will not start filling until this is shifted in +//bit order in byte -reverse, I guess? [((bt>>0)&1),((bt>>1)&1),((bt>>2)&1),((bt>>3)&1),((bt>>4)&1),((bt>>5)&1),((bt>>6)&1),((bt>>7)&1)] -at least in the mode that I read those in +#ifndef SYNC_16BIT +# define SYNC_16BIT 0xB24D #endif -#ifndef DELAY_ARM2AIR_AS_READER -#define DELAY_ARM2AIR_AS_READER (4*16 + 8*16 + 8 + 8 + 1) // 209 -#endif -#define AddCrc(data, len) compute_crc(CRC_FELICA, (data), (len), (data)+(len)+1, (data)+(len)) static uint32_t felica_timeout; -static uint32_t felica_nexttransfertime; +uint32_t felica_nexttransfertime; static uint32_t felica_lasttime_prox2air_start; static bool felica_field_active; -static void iso18092_setup(uint8_t fpga_minor_mode); -static uint8_t felica_select_card(felica_card_select_t *card); -static void TransmitFor18092_AsReader(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, uint8_t power, uint8_t highspeed); -static bool WaitForFelicaReply(uint16_t maxbytes); +felica_frame_t FelicaFrame; static void iso18092_set_timeout(uint32_t timeout) { felica_timeout = timeout + (DELAY_AIR2ARM_AS_READER + DELAY_ARM2AIR_AS_READER) / (FELICA_212K_CARRIER_PERIODS_PER_BIT * FELICA_BITS_PER_BYTE) + 2; @@ -72,60 +65,11 @@ static uint32_t iso18092_get_timeout(void) { return felica_timeout - (DELAY_AIR2ARM_AS_READER + DELAY_ARM2AIR_AS_READER) / (FELICA_212K_CARRIER_PERIODS_PER_BIT * FELICA_BITS_PER_BYTE) - 2; } -#ifndef FELICA_MAX_DATA_SIZE -// FeliCa length byte includes itself, so application level payload max is 254 bytes. -#define FELICA_MAX_DATA_SIZE 254 -#endif +bool felica_field_is_active(void) { + return felica_field_active; +} -#ifndef FELICA_MAX_RF_FRAME_SIZE -// 255 base length (max 254 data + 1 len byte) + 2 sync + 2 crc + 1 extra for safety. -#define FELICA_MAX_RF_FRAME_SIZE 260 -#endif - -//structure to hold outgoing NFC frame -static uint8_t frameSpace[FELICA_MAX_RF_FRAME_SIZE]; - -//structure to hold incoming NFC frame, used for ISO/IEC 18092-compatible frames -typedef struct { - enum { - STATE_UNSYNCD, - STATE_TRYING_SYNC, - STATE_GET_LENGTH, - STATE_GET_DATA, - STATE_GET_CRC, - STATE_FULL - } state; - - uint16_t shiftReg; //for synchronization and offset calculation - uint16_t shiftRegInv; // sync search helper while polarity is unknown - int posCnt; - bool crc_ok; - int rem_len; - uint16_t len; - uint8_t byte_offset; - uint8_t polarity; - uint32_t startTime; - uint32_t endTime; - uint8_t *framebytes; -//should be enough. maxlen is 255, 254 for data, 2 for sync, 2 for crc -// 0,1 -> SYNC, 2 - len, 3-(len+1)->data, then crc -} felica_frame_t; - -enum { - FELICA_POLARITY_UNKNOWN = 0, - FELICA_POLARITY_NORMAL = 1, - FELICA_POLARITY_INVERTED = 2 -}; - -static felica_frame_t FelicaFrame; - -//b2 4d is SYNC, 45645 in 16-bit notation, 10110010 01001101 binary. Frame will not start filling until this is shifted in -//bit order in byte -reverse, I guess? [((bt>>0)&1),((bt>>1)&1),((bt>>2)&1),((bt>>3)&1),((bt>>4)&1),((bt>>5)&1),((bt>>6)&1),((bt>>7)&1)] -at least in the mode that I read those in -#ifndef SYNC_16BIT -# define SYNC_16BIT 0xB24D -#endif - -static void FelicaFrameReset(felica_frame_t *f) { +void FelicaFrameReset(felica_frame_t *f) { f->state = STATE_UNSYNCD; f->posCnt = 0; f->shiftReg = 0; @@ -138,16 +82,17 @@ static void FelicaFrameReset(felica_frame_t *f) { f->startTime = 0; f->endTime = 0; } + static void FelicaFrameinit(felica_frame_t *f, uint8_t *data) { f->framebytes = data; FelicaFrameReset(f); } -static uint32_t felica_timer_to_carrier_periods(uint32_t timer_ticks, bool highspeed) { +uint32_t felica_timer_to_carrier_periods(uint32_t timer_ticks, bool highspeed) { return timer_ticks * (highspeed ? FELICA_424K_CARRIER_PERIODS_PER_BIT : FELICA_212K_CARRIER_PERIODS_PER_BIT); } -static uint32_t felica_get_rx_byte_start_time(void) { +uint32_t felica_get_rx_byte_start_time(void) { return (GetCountSspClk() & 0xfffffff8) - FELICA_BITS_PER_BYTE; } @@ -172,7 +117,7 @@ static void shiftInByte(felica_frame_t *f, uint8_t bt, uint32_t byte_start_time) } } -static void Process18092Byte(felica_frame_t *f, uint8_t bt, uint32_t byte_start_time) { +void Process18092Byte(felica_frame_t *f, uint8_t bt, uint32_t byte_start_time) { switch (f->state) { @@ -441,7 +386,7 @@ static void BuildFliteRdblk(const uint8_t *idm, uint8_t blocknum, const uint16_t AddCrc(frameSpace + 2, c - 2); } -static void TransmitFor18092_AsReader(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, uint8_t power, uint8_t highspeed) { +void TransmitFor18092_AsReaderEx(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, uint8_t power, uint8_t highspeed, bool reader2tag) { if (NYI_timing_NYI != NULL) { DbpString("Error: TransmitFor18092_AsReader does not check or set parameter NYI_timing_NYI"); @@ -504,12 +449,16 @@ static void TransmitFor18092_AsReader(const uint8_t *frame, uint16_t len, const felica_timer_to_carrier_periods(frame_start, highspeed) + DELAY_ARM2AIR_AS_READER, felica_timer_to_carrier_periods(frame_end, highspeed) + DELAY_ARM2AIR_AS_READER, NULL, - true + reader2tag ); felica_nexttransfertime = MAX(felica_nexttransfertime, felica_lasttime_prox2air_start + FELICA_REQUEST_GUARD_TIME); } +void TransmitFor18092_AsReader(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, uint8_t power, uint8_t highspeed) { + TransmitFor18092_AsReaderEx(frame, len, NYI_timing_NYI, power, highspeed, true); +} + // Wait for tag reply // stop when button is pressed // or return TRUE when command is captured @@ -600,7 +549,7 @@ bool WaitForFelicaReply(uint16_t maxbytes) { // Set up FeliCa communication (similar to iso14443a_setup) // field is setup for "Sending as Reader" -static void iso18092_setup(uint8_t fpga_minor_mode) { +bool iso18092_setup_ex(uint8_t fpga_minor_mode, uint32_t preserve_low_bytes) { LEDsoff(); #if defined XC3 @@ -610,7 +559,15 @@ static void iso18092_setup(uint8_t fpga_minor_mode) { #endif // allocate command receive buffer BigBuf_free(); - FelicaFrameinit(&FelicaFrame, BigBuf_calloc(FELICA_MAX_RF_FRAME_SIZE)); + if (preserve_low_bytes) { + set_tracelen(preserve_low_bytes); + } + + uint8_t *rx = BigBuf_calloc(FELICA_MAX_RF_FRAME_SIZE); + if (rx == NULL) { + return false; + } + FelicaFrameinit(&FelicaFrame, rx); felica_nexttransfertime = 2 * FELICA_212K_CARRIER_TO_TIMER_TICKS(DELAY_ARM2AIR_AS_READER); // iso18092_set_timeout(2120); // 106 * 20ms maximum start-up time of card @@ -638,16 +595,20 @@ static void iso18092_setup(uint8_t fpga_minor_mode) { LED_D_ON(); felica_field_active = true; + return true; } -static void felica_reset_frame_mode(void) { +void iso18092_setup(uint8_t fpga_minor_mode) { + (void)iso18092_setup_ex(fpga_minor_mode, 0); +} + +void felica_reset_frame_mode(void) { switch_off(); felica_field_active = false; //Resetting Frame mode (First set in fpgaloader.c) AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); } - //----------------------------------------------------------------------------- // RAW FeliCa commands. Send out commands and store answers. //----------------------------------------------------------------------------- @@ -686,7 +647,7 @@ void felica_sendraw(const PacketCommandNG *c) { // Preserve compatibility with existing commands that do not send CONNECT: // set up reader path when starting from field-off state. - if (do_connect || !felica_field_active) { + if (do_connect || felica_field_is_active() == false) { iso18092_setup(FPGA_HF_ISO18092_FLAG_READER | FPGA_HF_ISO18092_FLAG_NOMOD); } @@ -850,10 +811,7 @@ void felica_sniff(uint32_t samplesToSkip, uint32_t triggersToSkip) { } } } - switch_off(); - felica_field_active = false; - //reset framing - AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); + felica_reset_frame_mode(); Dbprintf("Felica sniffing done, tracelen: %i", BigBuf_get_traceLen()); reply_ng(CMD_HF_FELICA_SNIFF, retval, NULL, 0); @@ -997,11 +955,7 @@ void felica_sim_lite(const uint8_t *uid) { } } - switch_off(); - felica_field_active = false; - - // reset framing - AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); + felica_reset_frame_mode(); Dbprintf("FeliCa Lite-S emulator stopped. Trace length: %d ", BigBuf_get_traceLen()); reply_ng(CMD_HF_FELICALITE_SIMULATE, retval, NULL, 0); @@ -1076,11 +1030,7 @@ void felica_dump_lite_s(void) { break; } } - switch_off(); - felica_field_active = false; - - // Resetting Frame mode (First set in fpgaloader.c) - AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); + felica_reset_frame_mode(); // setting tracelen - important! it was set by buffer overflow before // iceman: is this still needed?!? diff --git a/armsrc/felica.h b/armsrc/felica.h index 2677dbfb9..137c0ca45 100644 --- a/armsrc/felica.h +++ b/armsrc/felica.h @@ -21,6 +21,70 @@ #include "common.h" #include "cmd.h" +// In the ISO18092/FeliCa FPGA bitstream, the SSC clock is the RF bit clock: +// fc/64 at 212 kbit/s and fc/32 at 424 kbit/s. Trace timestamps are stored +// in carrier periods, so convert at the logging boundary. +#define FELICA_BITS_PER_BYTE 8U +#define FELICA_212K_CARRIER_PERIODS_PER_BIT 64U +#define FELICA_424K_CARRIER_PERIODS_PER_BIT 32U +#define FELICA_212K_CARRIER_TO_TIMER_TICKS(x) (((x) + FELICA_212K_CARRIER_PERIODS_PER_BIT - 1U) / FELICA_212K_CARRIER_PERIODS_PER_BIT) + +#ifndef DELAY_AIR2ARM_AS_READER +#define DELAY_AIR2ARM_AS_READER (3 + 16 + 8 + 8*16 + 4*16 - 8*16) // 91 +#endif +#ifndef DELAY_ARM2AIR_AS_READER +#define DELAY_ARM2AIR_AS_READER (4*16 + 8*16 + 8 + 8 + 1) // 209 +#endif + +//structure to hold incoming NFC frame, used for ISO/IEC 18092-compatible frames +typedef struct { + enum { + STATE_UNSYNCD, + STATE_TRYING_SYNC, + STATE_GET_LENGTH, + STATE_GET_DATA, + STATE_GET_CRC, + STATE_FULL + } state; + + uint16_t shiftReg; //for synchronization and offset calculation + uint16_t shiftRegInv; // sync search helper while polarity is unknown + int posCnt; + bool crc_ok; + int rem_len; + uint16_t len; + uint8_t byte_offset; + uint8_t polarity; + uint32_t startTime; + uint32_t endTime; + uint8_t *framebytes; +//should be enough. maxlen is 255, 254 for data, 2 for sync, 2 for crc +// 0,1 -> SYNC, 2 - len, 3-(len+1)->data, then crc +} felica_frame_t; + +enum { + FELICA_POLARITY_UNKNOWN = 0, + FELICA_POLARITY_NORMAL = 1, + FELICA_POLARITY_INVERTED = 2 +}; + +extern uint32_t felica_nexttransfertime; +extern felica_frame_t FelicaFrame; + +bool felica_field_is_active(void); +void FelicaFrameReset(felica_frame_t *f); +uint32_t felica_timer_to_carrier_periods(uint32_t timer_ticks, bool highspeed); +uint32_t felica_get_rx_byte_start_time(void); +void Process18092Byte(felica_frame_t *f, uint8_t bt, uint32_t byte_start_time); +void TransmitFor18092_AsReader(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, + uint8_t power, uint8_t highspeed); +bool WaitForFelicaReply(uint16_t maxbytes); +void iso18092_setup(uint8_t fpga_minor_mode); +bool iso18092_setup_ex(uint8_t fpga_minor_mode, uint32_t preserve_low_bytes); +void felica_reset_frame_mode(void); +void TransmitFor18092_AsReaderEx(const uint8_t *frame, uint16_t len, const uint32_t *NYI_timing_NYI, + uint8_t power, uint8_t highspeed, bool reader2tag); + void felica_sendraw(const PacketCommandNG *c); void felica_sniff(uint32_t samplesToSkip, uint32_t triggersToSkip); void felica_sim_lite(const uint8_t *uid); diff --git a/armsrc/felicasim.c b/armsrc/felicasim.c new file mode 100644 index 000000000..06451dc46 --- /dev/null +++ b/armsrc/felicasim.c @@ -0,0 +1,941 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- +#include "felicasim.h" + +#include "felica.h" +#include "proxmark3_arm.h" +#include "BigBuf.h" +#include "util.h" +#include "protocols.h" +#include "crc16.h" +#include "fpgaloader.h" +#include "string.h" +#include "commonutil.h" +#include "dbprint.h" +#include "ticks.h" +#include "iso18.h" + +#define AddCrc(data, len) compute_crc(CRC_FELICA, (data), (len), (data)+(len)+1, (data)+(len)) + +#define FELICA_SIM_SYSTEM_MAX 16U +#define FELICA_SIM_STATUS_SERVICE_COUNT_ERROR 0xA1U +#define FELICA_SIM_STATUS_BLOCK_COUNT_ERROR 0xA2U +#define FELICA_SIM_STATUS_ILLEGAL_BLOCK_LIST_SERVICE_ORDER 0xA3U +#define FELICA_SIM_STATUS_ILLEGAL_SERVICE_CODE_LIST 0xA6U +#define FELICA_SIM_STATUS_ACCESS_MODE_ERROR 0xA7U +#define FELICA_SIM_STATUS_BLOCK_NOT_FOUND 0xA8U +#define FELICA_SIM_STATUS_AUTH_REQUIRED 0xB1U + +static uint32_t felica_sim_model_len; +static uint32_t felica_sim_model_uploaded; +static uint16_t felica_sim_model_crc; +static uint8_t felica_sim_rwe_error_location_indication = FELICA_SIM_RWE_ERROR_LOCATION_MASK; + +typedef struct { + const felica_sim_node_record_t *node; + uint16_t block_number; +} felica_sim_read_ref_t; + +static const felica_sim_system_record_t *felica_sim_systems(const felica_sim_model_header_t *hdr, const uint8_t *model) { + return (const felica_sim_system_record_t *)(model + hdr->system_offset); +} + +static const felica_sim_node_record_t *felica_sim_nodes(const felica_sim_model_header_t *hdr, const uint8_t *model) { + return (const felica_sim_node_record_t *)(model + hdr->node_offset); +} + +static const felica_sim_block_record_t *felica_sim_blocks(const felica_sim_model_header_t *hdr, const uint8_t *model) { + return (const felica_sim_block_record_t *)(model + hdr->block_offset); +} + +static const uint8_t *felica_sim_metadata(const felica_sim_model_header_t *hdr, const uint8_t *model) { + return model + hdr->metadata_offset; +} + +static bool felica_sim_range_fits(uint32_t offset, uint32_t count, uint32_t elem_size, uint32_t total_len) { + return offset <= total_len && elem_size != 0 && count <= ((total_len - offset) / elem_size); +} + +static uint16_t felica_sim_crc_model(uint8_t *model, uint32_t len) { + if (model == NULL || len < sizeof(felica_sim_model_header_t)) { + return 0; + } + + felica_sim_model_header_t *hdr = (felica_sim_model_header_t *)model; + const uint16_t stored_crc = hdr->model_crc; + hdr->model_crc = 0; + const uint16_t crc = Crc16ex(CRC_XMODEM, model, len); + hdr->model_crc = stored_crc; + return crc; +} + +static int felica_sim_validate_model(uint8_t *model, uint32_t len, const felica_sim_model_header_t **hdr_out) { + if (model == NULL || len < sizeof(felica_sim_model_header_t) || hdr_out == NULL) { + return PM3_EINVARG; + } + + const felica_sim_model_header_t *hdr = (const felica_sim_model_header_t *)model; + if (hdr->magic != FELICA_SIM_MODEL_MAGIC || + hdr->version != FELICA_SIM_MODEL_VERSION || + hdr->header_len != sizeof(felica_sim_model_header_t) || + hdr->total_len != len || + hdr->reserved != 0) { + return PM3_EINVARG; + } + + if (hdr->system_count == 0 || hdr->system_count > FELICA_SIM_SYSTEM_MAX) { + return PM3_EINVARG; + } + + if (felica_sim_crc_model(model, len) != hdr->model_crc) { + return PM3_ECRC; + } + + const uint32_t metadata_len = (uint32_t)hdr->specification_version_len + + hdr->product_information_len + + hdr->container_issue_information_len; + if (hdr->specification_version_len > FELICA_SIM_SPECIFICATION_VERSION_MAX_LEN || + hdr->product_information_len > FELICA_SIM_PRODUCT_INFORMATION_MAX_LEN || + (hdr->container_issue_information_len != 0 && + hdr->container_issue_information_len != FELICA_SIM_CONTAINER_ISSUE_INFORMATION_LEN)) { + return PM3_EINVARG; + } + + if (felica_sim_range_fits(hdr->system_offset, hdr->system_count, sizeof(felica_sim_system_record_t), len) == false || + felica_sim_range_fits(hdr->node_offset, hdr->node_count, sizeof(felica_sim_node_record_t), len) == false || + felica_sim_range_fits(hdr->block_offset, hdr->block_count, sizeof(felica_sim_block_record_t), len) == false || + hdr->metadata_offset > len || + metadata_len > (len - hdr->metadata_offset)) { + return PM3_EINVARG; + } + + const uint32_t expected_node_offset = hdr->system_offset + (hdr->system_count * sizeof(felica_sim_system_record_t)); + const uint32_t expected_block_offset = hdr->node_offset + (hdr->node_count * sizeof(felica_sim_node_record_t)); + const uint32_t expected_metadata_offset = hdr->block_offset + (hdr->block_count * sizeof(felica_sim_block_record_t)); + const uint32_t expected_total_len = expected_metadata_offset + metadata_len; + if (hdr->system_offset != hdr->header_len || + hdr->node_offset != expected_node_offset || + hdr->block_offset != expected_block_offset || + hdr->metadata_offset != expected_metadata_offset || + hdr->total_len != expected_total_len) { + return PM3_EINVARG; + } + + if (hdr->specification_version_len) { + const uint8_t *metadata = felica_sim_metadata(hdr, model); + if (hdr->specification_version_len < 4U || + hdr->specification_version_len != 4U + ((uint16_t)metadata[3] * 2U)) { + return PM3_EINVARG; + } + } + + const felica_sim_system_record_t *systems = felica_sim_systems(hdr, model); + const felica_sim_node_record_t *nodes = felica_sim_nodes(hdr, model); + const felica_sim_block_record_t *blocks = felica_sim_blocks(hdr, model); + + for (uint16_t i = 0; i < hdr->system_count; i++) { + if ((uint32_t)systems[i].first_node + systems[i].node_count > hdr->node_count) { + return PM3_EINVARG; + } + } + + const uint8_t valid_node_flags = FELICA_SIM_NODE_TYPE_MASK | + FELICA_SIM_NODE_HAS_DES_KEY_VERSION | + FELICA_SIM_NODE_HAS_AES_KEY_VERSION; + for (uint16_t i = 0; i < hdr->node_count; i++) { + const uint8_t node_type = nodes[i].flags & FELICA_SIM_NODE_TYPE_MASK; + if ((nodes[i].flags & ~valid_node_flags) != 0 || + node_type == 0 || + nodes[i].system_index >= hdr->system_count || + (uint32_t)nodes[i].first_block + nodes[i].block_count > hdr->block_count) { + return PM3_EINVARG; + } + } + + for (uint16_t i = 0; i < hdr->block_count; i++) { + if (blocks[i].node_index >= hdr->node_count) { + return PM3_EINVARG; + } + } + + *hdr_out = hdr; + return PM3_SUCCESS; +} + +static bool felica_sim_enc_has_aes(uint8_t encryption_identifier) { + switch (encryption_identifier) { + case FELICA_ENCRYPTION_IDENTIFIER_AES128: + case FELICA_ENCRYPTION_IDENTIFIER_AES128_DES112: + case FELICA_ENCRYPTION_IDENTIFIER_AES128_DES56: + return true; + default: + return false; + } +} + +static bool felica_sim_enc_has_des(uint8_t encryption_identifier) { + switch (encryption_identifier) { + case FELICA_ENCRYPTION_IDENTIFIER_AES128_DES112: + case FELICA_ENCRYPTION_IDENTIFIER_AES128_DES56: + case FELICA_ENCRYPTION_IDENTIFIER_DES112: + case FELICA_ENCRYPTION_IDENTIFIER_DES56: + return true; + default: + return false; + } +} + +static uint8_t felica_sim_effective_encryption_identifier(const felica_sim_system_record_t *system) { + return system->encryption_identifier ? system->encryption_identifier : FELICA_ENCRYPTION_IDENTIFIER_DES56; +} + +static uint8_t felica_sim_node_type(const felica_sim_node_record_t *node) { + return node ? (node->flags & FELICA_SIM_NODE_TYPE_MASK) : 0; +} + +static bool felica_sim_node_is_service(const felica_sim_node_record_t *node) { + return felica_sim_node_type(node) == FELICA_SIM_NODE_TYPE_SERVICE; +} + +static bool felica_sim_node_is_area(const felica_sim_node_record_t *node) { + return felica_sim_node_type(node) == FELICA_SIM_NODE_TYPE_AREA; +} + +static bool felica_sim_node_is_system(const felica_sim_node_record_t *node) { + return felica_sim_node_type(node) == FELICA_SIM_NODE_TYPE_SYSTEM; +} + +static bool felica_sim_service_allows_read_without_encryption(const felica_sim_node_record_t *node) { + if (felica_sim_node_is_service(node) == false) { + return false; + } + + const uint8_t attribute = node->node_code_le & FELICA_NODE_ATTRIBUTE_MASK; + return (attribute & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) && + ((attribute & FELICA_SERVICE_ATTRIBUTE_PIN_REQUIRED) == 0); +} + +static uint8_t felica_sim_response_begin(uint8_t *resp, uint8_t command) { + resp[0] = 0xb2; + resp[1] = 0x4d; + resp[2] = 0; + resp[3] = command; + return 4; +} + +static uint16_t felica_sim_response_finish(uint8_t *resp, uint16_t pos) { + if (pos < 4 || (pos - 2) > 0xFFU) { + return 0; + } + + resp[2] = (uint8_t)(pos - 2); + AddCrc(resp + 2, resp[2]); + return resp[2] + 4U; +} + +static void felica_sim_append_idm(uint8_t *resp, uint16_t *pos, const felica_sim_system_record_t *system) { + memcpy(resp + *pos, system->idm, sizeof(system->idm)); + *pos += sizeof(system->idm); +} + +static uint16_t felica_sim_rwe_error_response(uint8_t *resp, const felica_sim_system_record_t *system, uint8_t status1, uint8_t status2) { + uint16_t pos = felica_sim_response_begin(resp, FELICA_RDBLK_ACK); + felica_sim_append_idm(resp, &pos, system); + resp[pos++] = status1; + resp[pos++] = status2; + return felica_sim_response_finish(resp, pos); +} + +static uint8_t felica_sim_rwe_error_status1(uint8_t list_index) { + switch (felica_sim_rwe_error_location_indication) { + case FELICA_SIM_RWE_ERROR_LOCATION_INDEX: + return list_index + 1U; + case FELICA_SIM_RWE_ERROR_LOCATION_FLAG: + return 0xFFU; + case FELICA_SIM_RWE_ERROR_LOCATION_MASK: + default: + // Bit-data location wraps: bit 0 indicates the 1st or 9th list element. + return (uint8_t)(1U << (list_index & 0x07U)); + } +} + +static const felica_sim_system_record_t *felica_sim_find_system_by_code(const felica_sim_model_header_t *hdr, const uint8_t *model, uint16_t code, uint16_t *index_out) { + const felica_sim_system_record_t *systems = felica_sim_systems(hdr, model); + for (uint16_t i = 0; i < hdr->system_count; i++) { + if (systems[i].system_code == code) { + if (index_out) { + *index_out = i; + } + return &systems[i]; + } + } + return NULL; +} + +static const felica_sim_node_record_t *felica_sim_find_node(const felica_sim_model_header_t *hdr, const uint8_t *model, + const felica_sim_system_record_t *system, uint16_t node_code_le) { + const felica_sim_node_record_t *nodes = felica_sim_nodes(hdr, model); + const uint16_t end = system->first_node + system->node_count; + for (uint16_t i = system->first_node; i < end; i++) { + if (nodes[i].node_code_le == node_code_le) { + return &nodes[i]; + } + } + return NULL; +} + +static const felica_sim_block_record_t *felica_sim_find_block(const felica_sim_model_header_t *hdr, const uint8_t *model, + const felica_sim_node_record_t *node, uint16_t block_number) { + const felica_sim_block_record_t *blocks = felica_sim_blocks(hdr, model); + const uint16_t end = node->first_block + node->block_count; + for (uint16_t i = node->first_block; i < end; i++) { + if (blocks[i].block_number == block_number) { + return &blocks[i]; + } + } + return NULL; +} + +static bool felica_sim_idm_matches(const uint8_t *req, const felica_sim_system_record_t *system) { + return memcmp(req + 4, system->idm, sizeof(system->idm)) == 0; +} + +static const uint8_t *felica_sim_specification_version_data(const felica_sim_model_header_t *hdr, const uint8_t *model) { + return hdr->specification_version_len ? felica_sim_metadata(hdr, model) : NULL; +} + +static const uint8_t *felica_sim_product_information_data(const felica_sim_model_header_t *hdr, const uint8_t *model) { + if (hdr->product_information_len == 0) { + return NULL; + } + + return felica_sim_metadata(hdr, model) + hdr->specification_version_len; +} + +static const uint8_t *felica_sim_container_issue_information_data(const felica_sim_model_header_t *hdr, const uint8_t *model) { + if (hdr->container_issue_information_len == 0) { + return NULL; + } + + return felica_sim_metadata(hdr, model) + hdr->specification_version_len + hdr->product_information_len; +} + +static uint16_t felica_sim_process_polling(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + uint16_t *active_system_index, uint8_t *resp) { + if (req_len != 6) { + return 0; + } + + const uint16_t requested_system_code = ((uint16_t)req[4] << 8) | req[5]; + uint16_t selected_index = 0; + const felica_sim_system_record_t *selected = NULL; + if (requested_system_code == 0xFFFFU) { + selected = felica_sim_systems(hdr, model); + selected_index = 0; + } else { + selected = felica_sim_find_system_by_code(hdr, model, requested_system_code, &selected_index); + if (selected == NULL) { + return 0; + } + } + + const uint8_t request_code = req[6]; + if (request_code > 0x02U) { + return 0; + } + + *active_system_index = selected_index; + + static uint8_t timeslot = 0; + if (timeslot > req[7]) { + timeslot = 0; + } + felica_nexttransfertime = GetCountSspClk() - + FELICA_212K_CARRIER_TO_TIMER_TICKS(DELAY_AIR2ARM_AS_READER + DELAY_ARM2AIR_AS_READER) + + (512 + timeslot * 256) + 1; + timeslot++; + + uint16_t pos = felica_sim_response_begin(resp, FELICA_POLL_ACK); + memcpy(resp + pos, selected->idm, sizeof(selected->idm)); + pos += sizeof(selected->idm); + memcpy(resp + pos, selected->pmm, sizeof(selected->pmm)); + pos += sizeof(selected->pmm); + + if (request_code == 0x01U) { + resp[pos++] = (selected->system_code >> 8) & 0xFFU; + resp[pos++] = selected->system_code & 0xFFU; + } else if (request_code == 0x02U) { + resp[pos++] = 0x00; + resp[pos++] = 0x01; + } + + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request_system_code(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len != 10 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_REQSYSCODE_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = (uint8_t)hdr->system_count; + const felica_sim_system_record_t *systems = felica_sim_systems(hdr, model); + for (uint16_t i = 0; i < hdr->system_count; i++) { + resp[pos++] = (systems[i].system_code >> 8) & 0xFFU; + resp[pos++] = systems[i].system_code & 0xFFU; + } + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request_response(const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len != 10 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_REQRESP_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = 0x00; + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request_specification_version(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + const uint8_t *specification_version = felica_sim_specification_version_data(hdr, model); + if (specification_version == NULL || + req_len != 12 || + felica_sim_idm_matches(req, active_system) == false || + req[12] != 0x00 || + req[13] != 0x00) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_REQUEST_SPEC_VERSION_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = 0x00; + resp[pos++] = 0x00; + memcpy(resp + pos, specification_version, hdr->specification_version_len); + pos += hdr->specification_version_len; + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_get_product_information(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + const uint8_t *product_information = felica_sim_product_information_data(hdr, model); + if (product_information == NULL || + req_len != 10 || + felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_GETPLATFORMINFO_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = 0x00; + resp[pos++] = 0x00; + resp[pos++] = (uint8_t)hdr->product_information_len; + memcpy(resp + pos, product_information, hdr->product_information_len); + pos += hdr->product_information_len; + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_get_container_issue_information(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + const uint8_t *container_issue_information = felica_sim_container_issue_information_data(hdr, model); + if (container_issue_information == NULL || + req_len != 12 || + felica_sim_idm_matches(req, active_system) == false || + req[12] != 0x00 || + req[13] != 0x00) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_GET_CONTAINER_ISSUE_INFO_ACK); + felica_sim_append_idm(resp, &pos, active_system); + memcpy(resp + pos, container_issue_information, hdr->container_issue_information_len); + pos += hdr->container_issue_information_len; + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request_service(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len < 11 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + const uint8_t node_count = req[12]; + const uint16_t expected_len = 11U + (2U * node_count); + if (node_count == 0 || req_len != expected_len) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_REQSRV_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = node_count; + for (uint8_t i = 0; i < node_count; i++) { + const uint16_t node_code_le = req[13 + (i * 2U)] | ((uint16_t)req[14 + (i * 2U)] << 8); + const felica_sim_node_record_t *node = felica_sim_find_node(hdr, model, active_system, node_code_le); + uint16_t key_version_le = 0xFFFFU; + if (node && (node->flags & FELICA_SIM_NODE_HAS_DES_KEY_VERSION)) { + key_version_le = node->des_key_version_le; + } + resp[pos++] = key_version_le & 0xFFU; + resp[pos++] = (key_version_le >> 8) & 0xFFU; + } + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request_service_v2(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len < 11 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + const uint8_t node_count = req[12]; + const uint16_t expected_len = 11U + (2U * node_count); + if (node_count == 0 || req_len != expected_len) { + return 0; + } + + const uint8_t encryption_identifier = felica_sim_effective_encryption_identifier(active_system); + const bool has_aes = felica_sim_enc_has_aes(encryption_identifier); + const bool has_des = felica_sim_enc_has_des(encryption_identifier); + const uint16_t response_len = 1U + 1U + 8U + 2U + 1U + 1U + (2U * node_count) + (has_des ? (2U * node_count) : 0U); + if (response_len > 0xFFU) { + return 0; + } + + uint16_t pos = felica_sim_response_begin(resp, FELICA_REQSRV2_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = 0x00; + resp[pos++] = 0x00; + resp[pos++] = encryption_identifier; + resp[pos++] = node_count; + + for (uint8_t pass = 0; pass < 2; pass++) { + if (pass == 1 && has_des == false) { + break; + } + + for (uint8_t i = 0; i < node_count; i++) { + const uint16_t node_code_le = req[13 + (i * 2U)] | ((uint16_t)req[14 + (i * 2U)] << 8); + const felica_sim_node_record_t *node = felica_sim_find_node(hdr, model, active_system, node_code_le); + uint16_t key_version_le = 0xFFFFU; + if (node) { + if (pass == 0 && has_aes && (node->flags & FELICA_SIM_NODE_HAS_AES_KEY_VERSION)) { + key_version_le = node->aes_key_version_le; + } else if (pass == 1 && (node->flags & FELICA_SIM_NODE_HAS_DES_KEY_VERSION)) { + key_version_le = node->des_key_version_le; + } + } + resp[pos++] = key_version_le & 0xFFU; + resp[pos++] = (key_version_le >> 8) & 0xFFU; + } + } + + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_search_service_code(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len != 12 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + const uint16_t iterator = req[12] | ((uint16_t)req[13] << 8); + uint16_t pos = felica_sim_response_begin(resp, FELICA_SRCHSYSCODE_ACK); + felica_sim_append_idm(resp, &pos, active_system); + + const felica_sim_node_record_t *nodes = felica_sim_nodes(hdr, model); + const felica_sim_node_record_t *node = NULL; + const uint16_t end = active_system->first_node + active_system->node_count; + uint16_t visible_index = 0; + for (uint16_t i = active_system->first_node; i < end; i++) { + if (felica_sim_node_is_system(&nodes[i])) { + continue; + } + if (visible_index == iterator) { + node = &nodes[i]; + break; + } + visible_index++; + } + + if (node == NULL) { + resp[pos++] = 0xFF; + resp[pos++] = 0xFF; + return felica_sim_response_finish(resp, pos); + } + + resp[pos++] = node->node_code_le & 0xFFU; + resp[pos++] = (node->node_code_le >> 8) & 0xFFU; + if (felica_sim_node_is_area(node)) { + resp[pos++] = node->end_code_le & 0xFFU; + resp[pos++] = (node->end_code_le >> 8) & 0xFFU; + } + + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_read_without_encryption(const felica_sim_model_header_t *hdr, const uint8_t *model, + const uint8_t *req, uint16_t req_len, + const felica_sim_system_record_t *active_system, uint8_t *resp) { + if (req_len < 14 || felica_sim_idm_matches(req, active_system) == false) { + return 0; + } + + const uint16_t end = 2U + req_len; + const uint8_t service_count = req[12]; + if (service_count == 0 || service_count > 16U) { + return felica_sim_rwe_error_response(resp, active_system, 0xFFU, FELICA_SIM_STATUS_SERVICE_COUNT_ERROR); + } + + uint16_t pos = 13U; + if ((uint16_t)(pos + (2U * service_count) + 1U) > end) { + return 0; + } + + const felica_sim_node_record_t *service_nodes[16] = {0}; + for (uint8_t i = 0; i < service_count; i++) { + const uint16_t service_code_le = req[pos] | ((uint16_t)req[pos + 1] << 8); + pos += 2; + service_nodes[i] = felica_sim_find_node(hdr, model, active_system, service_code_le); + } + + const uint8_t block_count = req[pos++]; + if (block_count == 0 || block_count > 15U) { + return felica_sim_rwe_error_response(resp, active_system, 0xFFU, FELICA_SIM_STATUS_BLOCK_COUNT_ERROR); + } + + felica_sim_read_ref_t refs[15] = {0}; + for (uint8_t i = 0; i < block_count; i++) { + if (pos >= end) { + return 0; + } + + const uint8_t d0 = req[pos++]; + const uint8_t service_order = d0 & 0x0FU; + const uint8_t access_mode = (d0 >> 4) & 0x07U; + const bool extended = (d0 & 0x80U) == 0; + if (service_order >= service_count) { + return felica_sim_rwe_error_response(resp, active_system, felica_sim_rwe_error_status1(i), FELICA_SIM_STATUS_ILLEGAL_BLOCK_LIST_SERVICE_ORDER); + } + if (access_mode != 0U) { + return felica_sim_rwe_error_response(resp, active_system, felica_sim_rwe_error_status1(i), FELICA_SIM_STATUS_ACCESS_MODE_ERROR); + } + + uint16_t block_number = 0; + if (extended) { + if ((uint16_t)(pos + 2U) > end) { + return 0; + } + block_number = req[pos] | ((uint16_t)req[pos + 1] << 8); + pos += 2; + } else { + if ((uint16_t)(pos + 1U) > end) { + return 0; + } + block_number = req[pos++]; + } + + refs[i].node = service_nodes[service_order]; + refs[i].block_number = block_number; + if (felica_sim_node_is_service(refs[i].node) == false) { + return felica_sim_rwe_error_response(resp, active_system, felica_sim_rwe_error_status1(i), FELICA_SIM_STATUS_ILLEGAL_SERVICE_CODE_LIST); + } + if (felica_sim_service_allows_read_without_encryption(refs[i].node) == false) { + return felica_sim_rwe_error_response(resp, active_system, felica_sim_rwe_error_status1(i), FELICA_SIM_STATUS_AUTH_REQUIRED); + } + if (felica_sim_find_block(hdr, model, refs[i].node, block_number) == NULL) { + return felica_sim_rwe_error_response(resp, active_system, felica_sim_rwe_error_status1(i), FELICA_SIM_STATUS_BLOCK_NOT_FOUND); + } + } + + if (pos != end) { + return 0; + } + + pos = felica_sim_response_begin(resp, FELICA_RDBLK_ACK); + felica_sim_append_idm(resp, &pos, active_system); + resp[pos++] = 0x00; + resp[pos++] = 0x00; + resp[pos++] = block_count; + + for (uint8_t i = 0; i < block_count; i++) { + const felica_sim_block_record_t *block = felica_sim_find_block(hdr, model, refs[i].node, refs[i].block_number); + if (block == NULL || (uint16_t)(pos + 16U) > (FELICA_MAX_RF_FRAME_SIZE - 2U)) { + return 0; + } + memcpy(resp + pos, block->data, sizeof(block->data)); + pos += sizeof(block->data); + } + + return felica_sim_response_finish(resp, pos); +} + +static uint16_t felica_sim_process_request(const felica_sim_model_header_t *hdr, const uint8_t *model, + const felica_frame_t *request, uint16_t *active_system_index, + uint8_t *resp) { + if (request == NULL || request->crc_ok == false || request->len < 8 || request->framebytes[2] < 2) { + return 0; + } + + const uint8_t *req = request->framebytes; + const uint16_t req_len = req[2]; + if ((uint16_t)(req_len + 4U) != request->len) { + return 0; + } + + const felica_sim_system_record_t *systems = felica_sim_systems(hdr, model); + const felica_sim_system_record_t *active_system = &systems[*active_system_index]; + + switch (req[3]) { + case FELICA_POLL_REQ: + return felica_sim_process_polling(hdr, model, req, req_len, active_system_index, resp); + case FELICA_REQSYSCODE_REQ: + return felica_sim_process_request_system_code(hdr, model, req, req_len, active_system, resp); + case FELICA_REQRESP_REQ: + return felica_sim_process_request_response(req, req_len, active_system, resp); + case FELICA_REQUEST_SPEC_VERSION_REQ: + return felica_sim_process_request_specification_version(hdr, model, req, req_len, active_system, resp); + case FELICA_GETPLATFORMINFO_REQ: + return felica_sim_process_get_product_information(hdr, model, req, req_len, active_system, resp); + case FELICA_GET_CONTAINER_ISSUE_INFO_REQ: + return felica_sim_process_get_container_issue_information(hdr, model, req, req_len, active_system, resp); + case FELICA_REQSRV_REQ: + return felica_sim_process_request_service(hdr, model, req, req_len, active_system, resp); + case FELICA_REQSRV2_REQ: + return felica_sim_process_request_service_v2(hdr, model, req, req_len, active_system, resp); + case FELICA_SRCHSYSCODE_REQ: + return felica_sim_process_search_service_code(hdr, model, req, req_len, active_system, resp); + case FELICA_RDBLK_REQ: + return felica_sim_process_read_without_encryption(hdr, model, req, req_len, active_system, resp); + default: + return 0; + } +} + +static uint32_t felica_sim_trace_offset(uint32_t model_len) { + return (model_len + 3U) & ~3U; +} + +static void felica_sim_log_request(const felica_frame_t *request) { + if (request == NULL) { + return; + } + + LogTrace( + request->framebytes, + request->len, + felica_timer_to_carrier_periods(request->startTime, false) - DELAY_AIR2ARM_AS_READER, + felica_timer_to_carrier_periods(request->endTime, false) - DELAY_AIR2ARM_AS_READER, + NULL, + true + ); +} + +static void felica_sim_preserve_trace(uint32_t trace_offset) { + set_tracing(false); + + const uint32_t trace_len = BigBuf_get_traceLen(); + if (trace_len <= trace_offset) { + clear_trace(); + return; + } + + const uint32_t captured_len = trace_len - trace_offset; + memmove(BigBuf_get_addr(), BigBuf_get_addr() + trace_offset, captured_len); + set_tracelen(captured_len); +} + +static int felica_sim_standard_loop(const felica_sim_model_header_t *hdr, const uint8_t *model) { + const uint32_t model_len = hdr->total_len; + const uint32_t trace_offset = felica_sim_trace_offset(hdr->total_len); + if (iso18092_setup_ex(FPGA_HF_ISO18092_FLAG_NOMOD, trace_offset) == false) { + return PM3_EMALLOC; + } + + const felica_sim_model_header_t *validated_hdr = NULL; + int retval = felica_sim_validate_model((uint8_t *)model, model_len, &validated_hdr); + if (retval != PM3_SUCCESS) { + felica_reset_frame_mode(); + return retval; + } + hdr = validated_hdr; + + set_tracelen(trace_offset); + set_tracing(true); + + Dbprintf("FeliCa Standard simulation start. Systems: %u, nodes: %u, blocks: %u", + hdr->system_count, hdr->node_count, hdr->block_count); + + retval = PM3_SUCCESS; + uint16_t active_system_index = 0; + uint8_t resp[FELICA_MAX_RF_FRAME_SIZE] = {0}; + + uint8_t flip = 0; + uint16_t checker = 0; + for (;;) { + WDT_HIT(); + + if (flip == 3) { + if (data_available()) { + retval = PM3_EOPABORTED; + break; + } + flip = 0; + } + + if (checker >= 3000) { + if (BUTTON_PRESS()) { + retval = PM3_EOPABORTED; + break; + } + flip++; + checker = 0; + } + ++checker; + + if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) { + uint8_t dist = (uint8_t)(AT91C_BASE_SSC->SSC_RHR); + Process18092Byte(&FelicaFrame, dist, felica_get_rx_byte_start_time()); + + if (FelicaFrame.state == STATE_FULL) { + if (FelicaFrame.crc_ok) { + felica_sim_log_request(&FelicaFrame); + const uint16_t resp_len = felica_sim_process_request(hdr, model, &FelicaFrame, &active_system_index, resp); + if (resp_len) { + TransmitFor18092_AsReaderEx(resp, resp_len, NULL, 0, 0, false); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO18092 | FPGA_HF_ISO18092_FLAG_NOMOD); + } + } + FelicaFrameReset(&FelicaFrame); + } + } + } + + felica_reset_frame_mode(); + felica_sim_preserve_trace(trace_offset); + set_tracing(true); + Dbprintf("FeliCa Standard emulator stopped. Trace length: %d", BigBuf_get_traceLen()); + return retval; +} + +void felicasim_standard(const PacketCommandNG *c) { + if (c == NULL || c->ng == false || c->length < sizeof(felica_sim_upload_t)) { + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + + const felica_sim_upload_t *payload = (const felica_sim_upload_t *)c->data.asBytes; + const uint32_t total_len = payload->total_len; + const uint32_t offset = payload->offset; + const uint16_t chunk_len = payload->chunk_len; + + switch (payload->subcommand) { + case FELICA_SIM_CLEAR: + /* + * FpgaDownloadAndGo() clears BigBuf when the bitstream is not + * already loaded. Do this before accepting the model upload so a + * first simulator run cannot wipe the freshly uploaded model. + */ +#if defined XC3 + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); +#else + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_FELICA); +#endif + felica_sim_model_len = 0; + felica_sim_model_uploaded = 0; + felica_sim_model_crc = 0; + BigBuf_free(); + clear_trace(); + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_SUCCESS, NULL, 0); + return; + + case FELICA_SIM_LOAD: { + const uint32_t bigbuf_size = BigBuf_get_size(); + if (chunk_len == 0 || + c->length < sizeof(felica_sim_upload_t) + chunk_len || + total_len == 0 || + bigbuf_size < FELICA_SIM_RUNTIME_RESERVE || + total_len > bigbuf_size - FELICA_SIM_RUNTIME_RESERVE || + offset > total_len || + chunk_len > total_len - offset) { + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + + if (offset == 0) { + felica_sim_model_len = total_len; + felica_sim_model_uploaded = 0; + felica_sim_model_crc = payload->model_crc; + BigBuf_free(); + clear_trace(); + } else if (total_len != felica_sim_model_len || + payload->model_crc != felica_sim_model_crc || + offset != felica_sim_model_uploaded) { + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + + memcpy(BigBuf_get_addr() + offset, payload->data, chunk_len); + felica_sim_model_uploaded = offset + chunk_len; + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_SUCCESS, NULL, 0); + return; + } + + case FELICA_SIM_START: { + if (felica_sim_model_len == 0 || + felica_sim_model_uploaded != felica_sim_model_len || + total_len != felica_sim_model_len || + payload->model_crc != felica_sim_model_crc) { + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + + if (payload->rwe_error_location_indication > FELICA_SIM_RWE_ERROR_LOCATION_FLAG) { + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + felica_sim_rwe_error_location_indication = payload->rwe_error_location_indication; + + uint8_t *model = BigBuf_get_addr(); + const felica_sim_model_header_t *hdr = NULL; + int status = felica_sim_validate_model(model, felica_sim_model_len, &hdr); + if (status != PM3_SUCCESS) { + reply_ng(CMD_HF_FELICA_SIMULATE, status, NULL, 0); + return; + } + + status = felica_sim_standard_loop(hdr, model); + reply_ng(CMD_HF_FELICA_SIMULATE, status, NULL, 0); + return; + } + + default: + reply_ng(CMD_HF_FELICA_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } +} diff --git a/armsrc/felicasim.h b/armsrc/felicasim.h new file mode 100644 index 000000000..43e83fcd0 --- /dev/null +++ b/armsrc/felicasim.h @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- +#ifndef __FELICASIM_H +#define __FELICASIM_H + +#include "cmd.h" + +void felicasim_standard(const PacketCommandNG *c); + +#endif diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 880dea6b4..9236cae82 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -35,6 +35,7 @@ #include "platform_util.h" #include "cliparser.h" // cliparser #include "util_posix.h" // msleep, msclock +#include "jansson.h" #define FELICA_BLK_SIZE 16 @@ -69,7 +70,6 @@ #define FELICA_SYSTEM_CODE_MAX_COUNT 16U #define FELICA_DISCOVERED_SYSTEM_MAX_COUNT FELICA_SYSTEM_CODE_MAX_COUNT #define FELICA_SYSTEM_CODE_WILDCARD 0xFFFFU -#define FELICA_SYSTEM_NODE 0xFFFFU #define FELICA_SYSTEM_CODE_NFC_TYPE3 0x12FCU #define FELICA_SYSTEM_CODE_FELICA_LITE 0x88B4U #define FELICA_SYSTEM_CODE_FELICA_SECURE_ID 0x957AU @@ -81,62 +81,7 @@ #define FELICA_SYSTEM_LIST_JSON "felica/felica_system_code_list" #define FELICA_IC_CODE_LIST_JSON "felica/felica_ic_code_list" -#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) -#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000) -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC (0b001100) -#define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000) -#define FELICA_SERVICE_ATTRIBUTE_PIN_REQUIRED (0b100000) -#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110) -#define FELICA_NODE_ATTRIBUTE_MASK 0x3FU - -#define FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA 0x00U -#define FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA 0x01U -#define FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA_WITH_PIN 0x20U -#define FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA_WITH_PIN 0x21U -#define FELICA_AREA_ATTRIBUTE_END_ROOT_AREA 0x3EU -#define FELICA_AREA_ATTRIBUTE_END_SUB_AREA 0x3FU - -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITH_KEY 0x08U -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITHOUT_KEY 0x09U -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITH_KEY 0x0AU -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITHOUT_KEY 0x0BU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITH_KEY 0x0CU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITHOUT_KEY 0x0DU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITH_KEY 0x0EU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITHOUT_KEY 0x0FU -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITH_KEY 0x10U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITHOUT_KEY 0x11U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITH_KEY 0x12U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITHOUT_KEY 0x13U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITH_KEY 0x14U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITHOUT_KEY 0x15U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITH_KEY 0x16U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITHOUT_KEY 0x17U - -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITH_KEY_WITH_PIN 0x28U -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITHOUT_KEY_WITH_PIN 0x29U -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITH_KEY_WITH_PIN 0x2AU -#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITHOUT_KEY_WITH_PIN 0x2BU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITH_KEY_WITH_PIN 0x2CU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITHOUT_KEY_WITH_PIN 0x2DU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITH_KEY_WITH_PIN 0x2EU -#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITHOUT_KEY_WITH_PIN 0x2FU -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITH_KEY_WITH_PIN 0x30U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITHOUT_KEY_WITH_PIN 0x31U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITH_KEY_WITH_PIN 0x32U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITHOUT_KEY_WITH_PIN 0x33U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITH_KEY_WITH_PIN 0x34U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITHOUT_KEY_WITH_PIN 0x35U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITH_KEY_WITH_PIN 0x36U -#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITHOUT_KEY_WITH_PIN 0x37U - #define FELICA_REQUEST_SERVICE_DISCOVERY_BATCH_SIZE 32U -#define FELICA_ENCRYPTION_IDENTIFIER_AES128 0x4FU -#define FELICA_ENCRYPTION_IDENTIFIER_AES128_DES112 0x43U -#define FELICA_ENCRYPTION_IDENTIFIER_AES128_DES56 0x41U -#define FELICA_ENCRYPTION_IDENTIFIER_DES112 0x3FU -#define FELICA_ENCRYPTION_IDENTIFIER_DES56 0x2FU #define FELICA_MAX_NODE_NUMBER 0x03FFU #define FELICA_LITE_NODE_DISCOVERY_MAX_NODE_NUMBER 16U #define FELICA_PRESENCE_SERVICE_CODE_LE ((uint16_t)FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITHOUT_KEY) @@ -1155,7 +1100,7 @@ static bool felica_block_number_from_block_list_element(const uint8_t *block_lis } if (block_list_element_len == 3U) { - *block_number = ((uint16_t)block_list_element[1] << 8) | block_list_element[2]; + *block_number = block_list_element[1] | ((uint16_t)block_list_element[2] << 8); return true; } @@ -5294,16 +5239,20 @@ static int CmdHFFelicaReadPlain(const char *Cmd) { // main loop block reads if (all_block_list_elements) { - uint16_t last_blockno = 0xFF; - if (long_block_numbers) { - last_blockno = 0xFFFF; - } + const uint32_t last_blockno = long_block_numbers ? 0xFFFFU : 0xFFU; - for (uint16_t i = 0x00; i < last_blockno; i++) { - data[15] = i; + for (uint32_t i = 0x00; i <= last_blockno; i++) { + if (long_block_numbers) { + data[14] &= 0x7FU; + data[15] = (uint8_t)(i & 0xFFU); + data[16] = (uint8_t)((i >> 8) & 0xFFU); + } else { + data[14] |= 0x80U; + data[15] = (uint8_t)(i & 0xFFU); + } felica_read_without_encryption_response_t rd_noCry_resp; if ((send_read_without_encryption(flags, datalen, data, 0, &rd_noCry_resp) == PM3_SUCCESS)) { - print_read_without_encryption_response(&rd_noCry_resp, i); + print_read_without_encryption_response(&rd_noCry_resp, (uint16_t)i); } else { break; } @@ -6080,6 +6029,874 @@ static int CmdHFFelicaSniff(const char *Cmd) { return PM3_SUCCESS; } +typedef struct { + uint8_t *model; + size_t model_len; + uint16_t system_count; + uint16_t node_count; + uint16_t block_count; +} felica_sim_model_t; + +typedef struct { + felica_sim_system_record_t *systems; + felica_sim_node_record_t *nodes; + felica_sim_block_record_t *blocks; + uint8_t specification_version[FELICA_SIM_SPECIFICATION_VERSION_MAX_LEN]; + size_t specification_version_len; + uint8_t product_information[FELICA_SIM_PRODUCT_INFORMATION_MAX_LEN]; + size_t product_information_len; + uint8_t container_issue_information[FELICA_SIM_CONTAINER_ISSUE_INFORMATION_LEN]; + size_t container_issue_information_len; + size_t system_count; + size_t system_capacity; + size_t node_count; + size_t node_capacity; + size_t block_count; + size_t block_capacity; +} felica_sim_model_builder_t; + +static void felica_sim_model_free(felica_sim_model_t *model) { + if (model == NULL) { + return; + } + free(model->model); + memset(model, 0, sizeof(*model)); +} + +static void felica_sim_builder_free(felica_sim_model_builder_t *builder) { + if (builder == NULL) { + return; + } + free(builder->systems); + free(builder->nodes); + free(builder->blocks); + memset(builder, 0, sizeof(*builder)); +} + +static int felica_sim_reserve(void **ptr, size_t *capacity, size_t elem_size, size_t needed) { + if (needed <= *capacity) { + return PM3_SUCCESS; + } + + size_t new_capacity = (*capacity == 0) ? 16U : *capacity; + while (new_capacity < needed) { + if (new_capacity > (SIZE_MAX / 2U)) { + return PM3_EOVFLOW; + } + new_capacity *= 2U; + } + + if (new_capacity > (SIZE_MAX / elem_size)) { + return PM3_EOVFLOW; + } + + void *tmp = realloc(*ptr, new_capacity * elem_size); + if (tmp == NULL) { + return PM3_EMALLOC; + } + + *ptr = tmp; + *capacity = new_capacity; + return PM3_SUCCESS; +} + +static int felica_sim_builder_add_system(felica_sim_model_builder_t *builder, const felica_sim_system_record_t *system, size_t *index_out) { + if (builder->system_count >= UINT16_MAX) { + return PM3_EOVFLOW; + } + int ret = felica_sim_reserve((void **)&builder->systems, &builder->system_capacity, sizeof(*builder->systems), builder->system_count + 1U); + if (ret != PM3_SUCCESS) { + return ret; + } + + if (index_out) { + *index_out = builder->system_count; + } + builder->systems[builder->system_count++] = *system; + return PM3_SUCCESS; +} + +static int felica_sim_builder_add_node(felica_sim_model_builder_t *builder, const felica_sim_node_record_t *node, size_t *index_out) { + if (builder->node_count >= UINT16_MAX) { + return PM3_EOVFLOW; + } + int ret = felica_sim_reserve((void **)&builder->nodes, &builder->node_capacity, sizeof(*builder->nodes), builder->node_count + 1U); + if (ret != PM3_SUCCESS) { + return ret; + } + + if (index_out) { + *index_out = builder->node_count; + } + builder->nodes[builder->node_count++] = *node; + return PM3_SUCCESS; +} + +static int felica_sim_builder_add_block(felica_sim_model_builder_t *builder, const felica_sim_block_record_t *block) { + if (builder->block_count >= UINT16_MAX) { + return PM3_EOVFLOW; + } + int ret = felica_sim_reserve((void **)&builder->blocks, &builder->block_capacity, sizeof(*builder->blocks), builder->block_count + 1U); + if (ret != PM3_SUCCESS) { + return ret; + } + + builder->blocks[builder->block_count++] = *block; + return PM3_SUCCESS; +} + +static bool felica_sim_hex_to_bytes(const char *hex, uint8_t *out, size_t expected_len) { + if (hex == NULL || out == NULL || strlen(hex) != expected_len * 2U) { + return false; + } + + size_t len = 0; + return hexstr_to_byte_array(hex, out, &len) && len == expected_len; +} + +static bool felica_sim_parse_optional_hex_json(const json_t *value_json, uint8_t *out, + size_t min_len, size_t max_len, size_t *out_len) { + if (out == NULL || out_len == NULL || min_len > max_len) { + return false; + } + + *out_len = 0; + if (value_json == NULL || json_is_null(value_json)) { + return true; + } + if (json_is_string(value_json) == false) { + return false; + } + + const char *hex = json_string_value(value_json); + const size_t hex_len = strlen(hex); + if (hex_len == 0 || (hex_len & 1U)) { + return false; + } + + const size_t byte_len = hex_len / 2U; + if (byte_len < min_len || byte_len > max_len) { + return false; + } + + size_t parsed_len = 0; + if (hexstr_to_byte_array(hex, out, &parsed_len) == false || parsed_len != byte_len) { + return false; + } + + *out_len = byte_len; + return true; +} + +static bool felica_sim_validate_specification_version_data(const uint8_t *data, size_t data_len) { + if (data_len == 0) { + return true; + } + if (data == NULL || data_len < 4U) { + return false; + } + + const size_t expected_len = 4U + ((size_t)data[3] * 2U); + return data_len == expected_len && data_len <= FELICA_SIM_SPECIFICATION_VERSION_MAX_LEN; +} + +static bool felica_sim_parse_u16_be_hex(const char *hex, uint16_t *value_out) { + uint8_t bytes[2] = {0}; + if (value_out == NULL || felica_sim_hex_to_bytes(hex, bytes, sizeof(bytes)) == false) { + return false; + } + *value_out = ((uint16_t)bytes[0] << 8) | bytes[1]; + return true; +} + +static uint16_t felica_sim_swap16(uint16_t value) { + return (uint16_t)(((value & 0x00FFU) << 8) | ((value & 0xFF00U) >> 8)); +} + +static bool felica_sim_parse_key_version_json(const json_t *value_json, uint16_t *value_le_out, bool *has_value_out) { + if (value_le_out == NULL || has_value_out == NULL) { + return false; + } + + *value_le_out = 0; + *has_value_out = false; + if (value_json == NULL || json_is_null(value_json)) { + return true; + } + if (json_is_string(value_json) == false) { + return false; + } + + uint8_t bytes[2] = {0}; + if (felica_sim_hex_to_bytes(json_string_value(value_json), bytes, sizeof(bytes)) == false) { + return false; + } + + *value_le_out = bytes[0] | ((uint16_t)bytes[1] << 8); + *has_value_out = true; + return true; +} + +static bool felica_sim_parse_encryption_identifier_json(const json_t *value_json, uint8_t *identifier_out) { + if (identifier_out == NULL) { + return false; + } + + *identifier_out = 0; + if (value_json == NULL || json_is_null(value_json)) { + return true; + } + if (json_is_string(value_json) == false) { + return false; + } + + const char *identifier = json_string_value(value_json); + for (size_t i = 0; i < ARRAYLEN(FELICA_ENCRYPTION_IDENTIFIER_INFO); i++) { + if (strcmp(identifier, FELICA_ENCRYPTION_IDENTIFIER_INFO[i].name) == 0) { + *identifier_out = FELICA_ENCRYPTION_IDENTIFIER_INFO[i].identifier; + return true; + } + } + + uint8_t raw_identifier = 0; + if (felica_sim_hex_to_bytes(identifier, &raw_identifier, sizeof(raw_identifier)) == false) { + return false; + } + + if (felica_encryption_identifier_get_info(raw_identifier) == NULL) { + return false; + } + + *identifier_out = raw_identifier; + return true; +} + +static const char *felica_sim_rwe_error_location_indication_name(uint8_t mode) { + switch (mode) { + case FELICA_SIM_RWE_ERROR_LOCATION_MASK: + return "mask"; + case FELICA_SIM_RWE_ERROR_LOCATION_INDEX: + return "index"; + case FELICA_SIM_RWE_ERROR_LOCATION_FLAG: + return "flag"; + default: + return "unknown"; + } +} + +static bool felica_sim_parse_rwe_error_location_indication(const char *value, uint8_t *mode_out) { + if (value == NULL || value[0] == '\0' || mode_out == NULL) { + return false; + } + + char mode[16] = {0}; + const size_t len = strlen(value); + if (len >= sizeof(mode)) { + return false; + } + + for (size_t i = 0; i < len; i++) { + mode[i] = (char)tolower((unsigned char)value[i]); + } + + if (strcmp(mode, "mask") == 0 || strcmp(mode, "bitmask") == 0) { + *mode_out = FELICA_SIM_RWE_ERROR_LOCATION_MASK; + return true; + } + if (strcmp(mode, "index") == 0 || strcmp(mode, "number") == 0) { + *mode_out = FELICA_SIM_RWE_ERROR_LOCATION_INDEX; + return true; + } + if (strcmp(mode, "flag") == 0 || strcmp(mode, "ff") == 0) { + *mode_out = FELICA_SIM_RWE_ERROR_LOCATION_FLAG; + return true; + } + + return false; +} + +static int felica_sim_parse_metadata_json(felica_sim_model_builder_t *builder, const json_t *root) { + if (builder == NULL || json_is_object(root) == false) { + return PM3_EINVARG; + } + + if (felica_sim_parse_optional_hex_json(json_object_get(root, "specification_version"), + builder->specification_version, + 4U, sizeof(builder->specification_version), + &builder->specification_version_len) == false || + felica_sim_validate_specification_version_data(builder->specification_version, + builder->specification_version_len) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: root has invalid specification_version"); + return PM3_EINVARG; + } + + if (felica_sim_parse_optional_hex_json(json_object_get(root, "product_information"), + builder->product_information, + 1U, sizeof(builder->product_information), + &builder->product_information_len) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: root has invalid product_information"); + return PM3_EINVARG; + } + + if (felica_sim_parse_optional_hex_json(json_object_get(root, "container_issue_information"), + builder->container_issue_information, + sizeof(builder->container_issue_information), + sizeof(builder->container_issue_information), + &builder->container_issue_information_len) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: root has invalid container_issue_information"); + return PM3_EINVARG; + } + + return PM3_SUCCESS; +} + +static bool felica_sim_node_duplicate_exists(const felica_sim_model_builder_t *builder, const felica_sim_system_record_t *system, + uint8_t flags, uint16_t node_code_le, uint16_t end_code_le) { + const uint8_t node_type = flags & FELICA_SIM_NODE_TYPE_MASK; + const size_t end = (size_t)system->first_node + system->node_count; + for (size_t i = system->first_node; i < end; i++) { + const felica_sim_node_record_t *node = &builder->nodes[i]; + if ((node->flags & FELICA_SIM_NODE_TYPE_MASK) != node_type) { + continue; + } + if (node->node_code_le == node_code_le && + (node_type != FELICA_SIM_NODE_TYPE_AREA || node->end_code_le == end_code_le)) { + return true; + } + } + return false; +} + +static bool felica_sim_block_duplicate_exists(const felica_sim_model_builder_t *builder, const felica_sim_node_record_t *node, + uint16_t block_number) { + const size_t end = (size_t)node->first_block + node->block_count; + for (size_t i = node->first_block; i < end; i++) { + if (builder->blocks[i].block_number == block_number) { + return true; + } + } + return false; +} + +static int felica_sim_parse_node_json(felica_sim_model_builder_t *builder, const json_t *node_json, + size_t system_index, size_t node_json_index) { + if (json_is_object(node_json) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu is not an object", node_json_index); + return PM3_EINVARG; + } + + json_t *type_json = json_object_get(node_json, "type"); + json_t *code_json = json_object_get(node_json, "code"); + json_t *des_key_json = json_object_get(node_json, "des_key_version"); + json_t *aes_key_json = json_object_get(node_json, "aes_key_version"); + json_t *data_json = json_object_get(node_json, "data"); + + if (json_is_string(type_json) == false || json_is_string(code_json) == false || json_is_object(data_json) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu is missing type/code/data", node_json_index); + return PM3_EINVARG; + } + + const char *type = json_string_value(type_json); + const bool is_area = strcmp(type, "area") == 0; + const bool is_system = strcmp(type, "system") == 0; + const uint8_t node_type = is_system ? FELICA_SIM_NODE_TYPE_SYSTEM : + is_area ? FELICA_SIM_NODE_TYPE_AREA : + FELICA_SIM_NODE_TYPE_SERVICE; + if (is_area == false && is_system == false && strcmp(type, "service") != 0) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has unknown type `%s`", node_json_index, type); + return PM3_EINVARG; + } + + const char *code_hex = json_string_value(code_json); + const size_t code_len = strlen(code_hex); + if ((is_area && code_len != 4U && code_len != 8U) || (is_area == false && code_len != 4U)) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has invalid code length", node_json_index); + return PM3_EINVARG; + } + + char start_code_hex[5] = {0}; + memcpy(start_code_hex, code_hex, 4); + uint16_t node_code_be = 0; + if (felica_sim_parse_u16_be_hex(start_code_hex, &node_code_be) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has invalid code", node_json_index); + return PM3_EINVARG; + } + if (is_system && node_code_be != FELICA_SYSTEM_NODE) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system node %zu must use code FFFF", node_json_index); + return PM3_EINVARG; + } + if (is_system == false && node_code_be == FELICA_SYSTEM_NODE) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: non-system node %zu uses reserved system code FFFF", node_json_index); + return PM3_EINVARG; + } + + felica_sim_node_record_t node = { + .system_index = (uint16_t)system_index, + .node_code_le = felica_sim_swap16(node_code_be), + .end_code_le = is_area ? 0xFFFEU : 0, + .first_block = (uint16_t)builder->block_count, + .block_count = 0, + .des_key_version_le = 0xFFFFU, + .aes_key_version_le = 0xFFFFU, + .flags = node_type, + .reserved = 0, + }; + + if (is_area && code_len == 8U) { + char end_code_hex[5] = {0}; + memcpy(end_code_hex, code_hex + 4, 4); + uint16_t end_code_be = 0; + if (felica_sim_parse_u16_be_hex(end_code_hex, &end_code_be) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has invalid area end code", node_json_index); + return PM3_EINVARG; + } + node.end_code_le = felica_sim_swap16(end_code_be); + } + + bool has_key_version = false; + uint16_t key_version_le = 0; + if (felica_sim_parse_key_version_json(des_key_json, &key_version_le, &has_key_version) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has invalid DES key version", node_json_index); + return PM3_EINVARG; + } + if (has_key_version) { + node.des_key_version_le = key_version_le; + node.flags |= FELICA_SIM_NODE_HAS_DES_KEY_VERSION; + } + + key_version_le = 0; + if (felica_sim_parse_key_version_json(aes_key_json, &key_version_le, &has_key_version) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: node %zu has invalid AES key version", node_json_index); + return PM3_EINVARG; + } + if (has_key_version) { + node.aes_key_version_le = key_version_le; + node.flags |= FELICA_SIM_NODE_HAS_AES_KEY_VERSION; + } + + if (is_system && (node.flags & (FELICA_SIM_NODE_HAS_DES_KEY_VERSION | FELICA_SIM_NODE_HAS_AES_KEY_VERSION)) == 0) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system node `%s` is missing key version data", code_hex); + return PM3_EINVARG; + } + + const felica_sim_system_record_t *system = &builder->systems[system_index]; + if (felica_sim_node_duplicate_exists(builder, system, node.flags, node.node_code_le, node.end_code_le)) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: duplicate node code `%s`", code_hex); + return PM3_EINVARG; + } + + if ((is_area || is_system) && json_object_size(data_json) != 0) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: non-service node `%s` unexpectedly has block data", code_hex); + return PM3_EINVARG; + } + + size_t node_index = 0; + int ret = felica_sim_builder_add_node(builder, &node, &node_index); + if (ret != PM3_SUCCESS) { + return ret; + } + + const char *block_number_hex = NULL; + json_t *block_data_json = NULL; + json_object_foreach(data_json, block_number_hex, block_data_json) { + if (json_is_string(block_data_json) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: block `%s` value is not a string", block_number_hex); + return PM3_EINVARG; + } + + uint16_t block_number = 0; + if (felica_sim_parse_u16_be_hex(block_number_hex, &block_number) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: invalid block number `%s`", block_number_hex); + return PM3_EINVARG; + } + + if (felica_sim_block_duplicate_exists(builder, &builder->nodes[node_index], block_number)) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: duplicate block `%s` in node `%s`", block_number_hex, code_hex); + return PM3_EINVARG; + } + + felica_sim_block_record_t block = { + .node_index = (uint16_t)node_index, + .block_number = block_number, + .data = {0}, + }; + if (felica_sim_hex_to_bytes(json_string_value(block_data_json), block.data, sizeof(block.data)) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: block `%s` is not 16 bytes", block_number_hex); + return PM3_EINVARG; + } + + ret = felica_sim_builder_add_block(builder, &block); + if (ret != PM3_SUCCESS) { + return ret; + } + builder->nodes[node_index].block_count++; + } + + builder->systems[system_index].node_count++; + return PM3_SUCCESS; +} + +static int felica_sim_parse_system_json(felica_sim_model_builder_t *builder, const json_t *system_json, size_t system_json_index) { + if (json_is_object(system_json) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system %zu is not an object", system_json_index); + return PM3_EINVARG; + } + + json_t *code_json = json_object_get(system_json, "code"); + json_t *idm_json = json_object_get(system_json, "idm"); + json_t *pmm_json = json_object_get(system_json, "pmm"); + json_t *encryption_identifier_json = json_object_get(system_json, "encryption_identifier"); + json_t *nodes_json = json_object_get(system_json, "nodes"); + + if (json_is_string(code_json) == false || + json_is_string(idm_json) == false || + json_is_string(pmm_json) == false || + json_is_array(nodes_json) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system %zu is missing code/idm/pmm/nodes", system_json_index); + return PM3_EINVARG; + } + + uint16_t system_code = 0; + if (felica_sim_parse_u16_be_hex(json_string_value(code_json), &system_code) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system %zu has invalid system code", system_json_index); + return PM3_EINVARG; + } + + for (size_t i = 0; i < builder->system_count; i++) { + if (builder->systems[i].system_code == system_code) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: duplicate system code `%04X`", system_code); + return PM3_EINVARG; + } + } + + felica_sim_system_record_t system = { + .system_code = system_code, + .idm = {0}, + .pmm = {0}, + .encryption_identifier = 0, + .reserved = 0, + .first_node = (uint16_t)builder->node_count, + .node_count = 0, + }; + + if (felica_sim_hex_to_bytes(json_string_value(idm_json), system.idm, sizeof(system.idm)) == false || + felica_sim_hex_to_bytes(json_string_value(pmm_json), system.pmm, sizeof(system.pmm)) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system %zu has invalid IDm or PMm", system_json_index); + return PM3_EINVARG; + } + + if (felica_sim_parse_encryption_identifier_json(encryption_identifier_json, &system.encryption_identifier) == false) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system %zu has invalid encryption identifier", system_json_index); + return PM3_EINVARG; + } + + size_t system_index = 0; + int ret = felica_sim_builder_add_system(builder, &system, &system_index); + if (ret != PM3_SUCCESS) { + return ret; + } + + size_t node_index = 0; + json_t *node_json = NULL; + json_array_foreach(nodes_json, node_index, node_json) { + ret = felica_sim_parse_node_json(builder, node_json, system_index, node_index); + if (ret != PM3_SUCCESS) { + return ret; + } + } + + return PM3_SUCCESS; +} + +static int felica_sim_model_finalize(const felica_sim_model_builder_t *builder, felica_sim_model_t *model_out) { + const size_t systems_len = builder->system_count * sizeof(felica_sim_system_record_t); + const size_t nodes_len = builder->node_count * sizeof(felica_sim_node_record_t); + const size_t blocks_len = builder->block_count * sizeof(felica_sim_block_record_t); + const size_t metadata_len = builder->specification_version_len + + builder->product_information_len + + builder->container_issue_information_len; + const size_t model_len = sizeof(felica_sim_model_header_t) + systems_len + nodes_len + blocks_len + metadata_len; + + if (builder->system_count == 0 || + builder->system_count > FELICA_SYSTEM_CODE_MAX_COUNT || + builder->node_count > UINT16_MAX || + builder->block_count > UINT16_MAX || + builder->specification_version_len > UINT16_MAX || + builder->product_information_len > UINT16_MAX || + builder->container_issue_information_len > UINT16_MAX || + model_len > UINT32_MAX) { + return PM3_EOVFLOW; + } + + uint8_t *model = calloc(1, model_len); + if (model == NULL) { + return PM3_EMALLOC; + } + + felica_sim_model_header_t *hdr = (felica_sim_model_header_t *)model; + hdr->magic = FELICA_SIM_MODEL_MAGIC; + hdr->version = FELICA_SIM_MODEL_VERSION; + hdr->header_len = sizeof(*hdr); + hdr->total_len = (uint32_t)model_len; + hdr->model_crc = 0; + hdr->system_count = (uint16_t)builder->system_count; + hdr->node_count = (uint16_t)builder->node_count; + hdr->block_count = (uint16_t)builder->block_count; + hdr->system_offset = sizeof(*hdr); + hdr->node_offset = hdr->system_offset + systems_len; + hdr->block_offset = hdr->node_offset + nodes_len; + hdr->metadata_offset = hdr->block_offset + blocks_len; + hdr->specification_version_len = (uint16_t)builder->specification_version_len; + hdr->product_information_len = (uint16_t)builder->product_information_len; + hdr->container_issue_information_len = (uint16_t)builder->container_issue_information_len; + hdr->reserved = 0; + + if (systems_len) { + memcpy(model + hdr->system_offset, builder->systems, systems_len); + } + if (nodes_len) { + memcpy(model + hdr->node_offset, builder->nodes, nodes_len); + } + if (blocks_len) { + memcpy(model + hdr->block_offset, builder->blocks, blocks_len); + } + uint8_t *metadata = model + hdr->metadata_offset; + size_t metadata_pos = 0; + if (builder->specification_version_len) { + memcpy(metadata + metadata_pos, builder->specification_version, builder->specification_version_len); + metadata_pos += builder->specification_version_len; + } + if (builder->product_information_len) { + memcpy(metadata + metadata_pos, builder->product_information, builder->product_information_len); + metadata_pos += builder->product_information_len; + } + if (builder->container_issue_information_len) { + memcpy(metadata + metadata_pos, builder->container_issue_information, builder->container_issue_information_len); + } + hdr->model_crc = Crc16ex(CRC_XMODEM, model, model_len); + + model_out->model = model; + model_out->model_len = model_len; + model_out->system_count = hdr->system_count; + model_out->node_count = hdr->node_count; + model_out->block_count = hdr->block_count; + return PM3_SUCCESS; +} + +static int felica_sim_model_from_json(json_t *root, felica_sim_model_t *model_out) { + if (json_is_object(root) == false || model_out == NULL) { + return PM3_EINVARG; + } + + json_t *systems_json = json_object_get(root, "systems"); + if (json_is_array(systems_json) == false || json_array_size(systems_json) == 0) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: root must contain non-empty systems array"); + return PM3_EINVARG; + } + + if (json_array_size(systems_json) > FELICA_SYSTEM_CODE_MAX_COUNT) { + PrintAndLogEx(ERR, "Invalid FeliCa dump: system count exceeds %u", FELICA_SYSTEM_CODE_MAX_COUNT); + return PM3_EOVFLOW; + } + + felica_sim_model_builder_t builder = {0}; + int ret = felica_sim_parse_metadata_json(&builder, root); + if (ret != PM3_SUCCESS) { + return ret; + } + + size_t system_index = 0; + json_t *system_json = NULL; + json_array_foreach(systems_json, system_index, system_json) { + ret = felica_sim_parse_system_json(&builder, system_json, system_index); + if (ret != PM3_SUCCESS) { + felica_sim_builder_free(&builder); + return ret; + } + } + + ret = felica_sim_model_finalize(&builder, model_out); + felica_sim_builder_free(&builder); + if (ret != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Unable to build FeliCa simulator model"); + } + return ret; +} + +static int felica_sim_send_control(uint8_t subcommand, uint32_t total_len, uint32_t offset, uint16_t model_crc, + const uint8_t *data, uint16_t data_len, uint32_t timeout_ms) { + if (data_len > (PM3_CMD_DATA_SIZE - sizeof(felica_sim_upload_t))) { + return PM3_EOVFLOW; + } + + uint8_t packet[PM3_CMD_DATA_SIZE] = {0}; + felica_sim_upload_t *payload = (felica_sim_upload_t *)packet; + payload->subcommand = subcommand; + payload->total_len = total_len; + payload->offset = offset; + payload->model_crc = model_crc; + payload->chunk_len = data_len; + if (data_len && data) { + memcpy(payload->data, data, data_len); + } + + clearCommandBuffer(); + SendCommandNG(CMD_HF_FELICA_SIMULATE, packet, sizeof(*payload) + data_len); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_FELICA_SIMULATE, &resp, timeout_ms) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for simulator response"); + return PM3_ETIMEOUT; + } + + return resp.status; +} + +static int felica_sim_upload_model(const felica_sim_model_t *model) { + const felica_sim_model_header_t *hdr = (const felica_sim_model_header_t *)model->model; + int ret = felica_sim_send_control(FELICA_SIM_CLEAR, 0, 0, 0, NULL, 0, 5000); + if (ret != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Unable to clear FeliCa simulator state (%d)", ret); + return ret; + } + + const uint16_t chunk_size = PM3_CMD_DATA_SIZE - sizeof(felica_sim_upload_t); + uint32_t offset = 0; + PrintAndLogEx(INFO, "Uploading simulator model"); + PrintAndLogEx(INFO, "." NOLF); + fflush(stdout); + + g_conn.block_after_ACK = true; + while (offset < model->model_len) { + const uint16_t bytes_to_send = MIN(chunk_size, model->model_len - offset); + if (offset + bytes_to_send >= model->model_len) { + g_conn.block_after_ACK = false; + } + + ret = felica_sim_send_control(FELICA_SIM_LOAD, (uint32_t)model->model_len, offset, hdr->model_crc, + model->model + offset, bytes_to_send, 2500); + if (ret != PM3_SUCCESS) { + g_conn.block_after_ACK = false; + PrintAndLogEx(FAILED, "Unable to upload simulator model at offset %" PRIu32 " (%d)", offset, ret); + return ret; + } + + PrintAndLogEx(NORMAL, "." NOLF); + fflush(stdout); + offset += bytes_to_send; + } + g_conn.block_after_ACK = false; + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + +static int felica_sim_start(const felica_sim_model_t *model, uint8_t rwe_error_location_indication) { + const felica_sim_model_header_t *hdr = (const felica_sim_model_header_t *)model->model; + uint8_t packet[sizeof(felica_sim_upload_t)] = {0}; + felica_sim_upload_t *payload = (felica_sim_upload_t *)packet; + payload->subcommand = FELICA_SIM_START; + payload->total_len = (uint32_t)model->model_len; + payload->model_crc = hdr->model_crc; + payload->rwe_error_location_indication = rwe_error_location_indication; + + PrintAndLogEx(INFO, "RWE error location indication.. " _YELLOW_("%s"), + felica_sim_rwe_error_location_indication_name(rwe_error_location_indication)); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to abort simulation"); + PrintAndLogEx(INFO, "After abort, use " _YELLOW_("hf felica list") " to view captured simulator traffic"); + clearCommandBuffer(); + SendCommandNG(CMD_HF_FELICA_SIMULATE, packet, sizeof(packet)); + + PacketResponseNG resp; + bool abort_sent = false; + for (;;) { + if (abort_sent == false && kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(DEBUG, "\naborted via keyboard!"); + abort_sent = true; + msleep(300); + } + + if (WaitForResponseTimeout(CMD_HF_FELICA_SIMULATE, &resp, 1000)) { + if (resp.status == PM3_EOPABORTED) { + PrintAndLogEx(DEBUG, "Button pressed, user aborted"); + return PM3_SUCCESS; + } + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "FeliCa simulator stopped with status %d", resp.status); + return resp.status; + } + return PM3_SUCCESS; + } + } +} + +static int CmdHFFelicaSim(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf felica sim", + "Emulate a FeliCa Standard card from a JSON dump generated by `hf felica dump`.", + "hf felica sim -f hf-felica-dump.json\n" + "hf felica sim -f hf-felica-dump --read-without-encryption-error-location-indication index\n" + "hf felica sim -f hf-felica-dump --read-without-encryption-error-location-indication flag -v" + ); + void *argtable[] = { + arg_param_begin, + arg_str1("f", "file", "", "JSON dump file"), + arg_str0(NULL, "read-without-encryption-error-location-indication", + "", "Read Without Encryption error location indication (default: mask)"), + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + char filename[FILE_PATH_SIZE] = {0}; + int filename_len = sizeof(filename) - 1; + CLIGetStrWithReturn(ctx, 1, (uint8_t *)filename, &filename_len); + uint8_t rwe_error_location_indication = FELICA_SIM_RWE_ERROR_LOCATION_MASK; + struct arg_str *rwe_error_location_arg = arg_get_str(ctx, 2); + const char *rwe_error_location = rwe_error_location_arg->count ? rwe_error_location_arg->sval[0] : NULL; + if (rwe_error_location && + felica_sim_parse_rwe_error_location_indication(rwe_error_location, &rwe_error_location_indication) == false) { + PrintAndLogEx(ERR, "Invalid RWE error location indication `%s`; expected mask, index, or flag", rwe_error_location); + CLIParserFree(ctx); + return PM3_EINVARG; + } + + const bool verbose = arg_get_lit(ctx, 3); + CLIParserFree(ctx); + + json_t *root = NULL; + int ret = loadFileJSONroot(filename, (void **)&root, verbose); + if (ret != PM3_SUCCESS) { + return ret; + } + + felica_sim_model_t model = {0}; + ret = felica_sim_model_from_json(root, &model); + json_decref(root); + if (ret != PM3_SUCCESS) { + return ret; + } + + PrintAndLogEx(SUCCESS, "Prepared FeliCa Standard model: %u system(s), %u node(s), %u block(s), %zu byte(s)", + model.system_count, model.node_count, model.block_count, model.model_len); + if (g_pm3_capabilities.bigbuf_size && + model.model_len + FELICA_SIM_RUNTIME_RESERVE > g_pm3_capabilities.bigbuf_size) { + PrintAndLogEx(ERR, "Simulator model is too large for device BigBuf: %zu + %u reserve > %" PRIu32, + model.model_len, FELICA_SIM_RUNTIME_RESERVE, g_pm3_capabilities.bigbuf_size); + felica_sim_model_free(&model); + return PM3_EOVFLOW; + } + + ret = felica_sim_upload_model(&model); + if (ret == PM3_SUCCESS) { + ret = felica_sim_start(&model, rwe_error_location_indication); + } + + felica_sim_model_free(&model); + PrintAndLogEx(INFO, "Done!"); + return ret; +} + // uid hex static int CmdHFFelicaSimLite(const char *Cmd) { CLIParserContext *ctx; @@ -7127,6 +7944,7 @@ static command_t CommandTable[] = { {"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("FeliCa Standard") " -----------------------"}, {"dump", CmdHFFelicaDump, IfPm3Felica, "Wait for and try dumping FeliCa"}, {"discnodes", CmdHFFelicaDiscoverNodes, IfPm3Felica, "discover Area Code and Service Code nodes."}, + {"sim", CmdHFFelicaSim, IfPm3Felica, "Emulate FeliCa Standard from dump file"}, {"rqservice", CmdHFFelicaRequestService, IfPm3Felica, "verify the existence of Area and Service, and to acquire Key Version."}, {"rqresponse", CmdHFFelicaRequestResponse, IfPm3Felica, "verify the existence of a card and its Mode."}, {"scsvcode", CmdHFFelicaDumpServiceArea, IfPm3Felica, "acquire Area Code and Service Code."}, @@ -7148,7 +7966,6 @@ static command_t CommandTable[] = { {"litesim", CmdHFFelicaSimLite, IfPm3Felica, "Emulating ISO/18092 FeliCa Lite tag"}, {"liteauth", CmdHFFelicaAuthenticationLite, IfPm3Felica, "authenticate a card."}, {"litedump", CmdHFFelicaDumpLite, IfPm3Felica, "Wait for and try dumping FelicaLite"}, - // {"sim", CmdHFFelicaSim, IfPm3Felica, " -- Simulate ISO 18092/FeliCa tag"} {NULL, NULL, NULL, NULL} }; diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index affbba601..95f1a860a 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -258,6 +258,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf felica wrbl" }, { 0, "hf felica dump" }, { 0, "hf felica discnodes" }, + { 0, "hf felica sim" }, { 0, "hf felica rqservice" }, { 0, "hf felica rqresponse" }, { 0, "hf felica scsvcode" }, diff --git a/include/iso18.h b/include/iso18.h index 082b35d9f..a38749656 100644 --- a/include/iso18.h +++ b/include/iso18.h @@ -20,7 +20,68 @@ #include "common.h" +// FeliCa length byte includes itself, so application-level payload max is 254 bytes. +#define FELICA_MAX_DATA_SIZE 254U +// 255 base length (max 254 data + 1 len byte) + 2 sync + 2 crc + 1 extra for safety. +#define FELICA_MAX_RF_FRAME_SIZE 260U #define FELICA_SPECIFICATION_VERSION_MAX_OPTIONS 16U +#define FELICA_SYSTEM_NODE 0xFFFFU + +#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ 0x01U +#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY 0x02U +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS 0x08U +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC 0x0CU +#define FELICA_SERVICE_ATTRIBUTE_PURSE 0x10U +#define FELICA_SERVICE_ATTRIBUTE_PIN_REQUIRED 0x20U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD 0x06U +#define FELICA_NODE_ATTRIBUTE_MASK 0x3FU + +#define FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA 0x00U +#define FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA 0x01U +#define FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA_WITH_PIN 0x20U +#define FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA_WITH_PIN 0x21U +#define FELICA_AREA_ATTRIBUTE_END_ROOT_AREA 0x3EU +#define FELICA_AREA_ATTRIBUTE_END_SUB_AREA 0x3FU + +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITH_KEY 0x08U +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITHOUT_KEY 0x09U +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITH_KEY 0x0AU +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITHOUT_KEY 0x0BU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITH_KEY 0x0CU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITHOUT_KEY 0x0DU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITH_KEY 0x0EU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITHOUT_KEY 0x0FU +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITH_KEY 0x10U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITHOUT_KEY 0x11U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITH_KEY 0x12U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITHOUT_KEY 0x13U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITH_KEY 0x14U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITHOUT_KEY 0x15U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITH_KEY 0x16U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITHOUT_KEY 0x17U + +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITH_KEY_WITH_PIN 0x28U +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RW_WITHOUT_KEY_WITH_PIN 0x29U +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITH_KEY_WITH_PIN 0x2AU +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_RO_WITHOUT_KEY_WITH_PIN 0x2BU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITH_KEY_WITH_PIN 0x2CU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RW_WITHOUT_KEY_WITH_PIN 0x2DU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITH_KEY_WITH_PIN 0x2EU +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC_RO_WITHOUT_KEY_WITH_PIN 0x2FU +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITH_KEY_WITH_PIN 0x30U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RW_WITHOUT_KEY_WITH_PIN 0x31U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITH_KEY_WITH_PIN 0x32U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_CASHBACK_WITHOUT_KEY_WITH_PIN 0x33U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITH_KEY_WITH_PIN 0x34U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_DECREMENT_WITHOUT_KEY_WITH_PIN 0x35U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITH_KEY_WITH_PIN 0x36U +#define FELICA_SERVICE_ATTRIBUTE_PURSE_RO_WITHOUT_KEY_WITH_PIN 0x37U + +#define FELICA_ENCRYPTION_IDENTIFIER_AES128 0x4FU +#define FELICA_ENCRYPTION_IDENTIFIER_AES128_DES112 0x43U +#define FELICA_ENCRYPTION_IDENTIFIER_AES128_DES56 0x41U +#define FELICA_ENCRYPTION_IDENTIFIER_DES112 0x3FU +#define FELICA_ENCRYPTION_IDENTIFIER_DES56 0x2FU typedef enum FELICA_COMMAND { FELICA_CONNECT = (1 << 0), @@ -45,6 +106,90 @@ typedef struct { uint16_t tracelen; } PACKED felica_lite_dump_resp_t; +typedef enum FELICA_SIM_SUBCOMMAND { + FELICA_SIM_CLEAR = 0x00, + FELICA_SIM_LOAD = 0x01, + FELICA_SIM_START = 0x02, +} felica_sim_subcommand_t; + +typedef enum FELICA_SIM_RWE_ERROR_LOCATION_INDICATION { + FELICA_SIM_RWE_ERROR_LOCATION_MASK = 0x00, + FELICA_SIM_RWE_ERROR_LOCATION_INDEX = 0x01, + FELICA_SIM_RWE_ERROR_LOCATION_FLAG = 0x02, +} felica_sim_rwe_error_location_indication_t; + +#define FELICA_SIM_MODEL_MAGIC 0x31465346U // FSF1 +#define FELICA_SIM_MODEL_VERSION 3U +#define FELICA_SIM_UPLOAD_CHUNK_MAX (PM3_CMD_DATA_SIZE - sizeof(felica_sim_upload_t)) +#define FELICA_SIM_RUNTIME_RESERVE 512U +#define FELICA_SIM_SPECIFICATION_VERSION_MAX_LEN (4U + (FELICA_SPECIFICATION_VERSION_MAX_OPTIONS * 2U)) +#define FELICA_SIM_PRODUCT_INFORMATION_MAX_LEN 64U +#define FELICA_SIM_CONTAINER_ISSUE_INFORMATION_LEN 16U + +#define FELICA_SIM_NODE_TYPE_MASK 0x03U +#define FELICA_SIM_NODE_TYPE_SERVICE 0x01U +#define FELICA_SIM_NODE_TYPE_AREA 0x02U +#define FELICA_SIM_NODE_TYPE_SYSTEM 0x03U +#define FELICA_SIM_NODE_HAS_DES_KEY_VERSION 0x04U +#define FELICA_SIM_NODE_HAS_AES_KEY_VERSION 0x08U + +typedef struct { + uint8_t subcommand; + uint32_t total_len; + uint32_t offset; + uint16_t model_crc; + uint16_t chunk_len; + uint8_t rwe_error_location_indication; + uint8_t data[]; +} PACKED felica_sim_upload_t; + +typedef struct { + uint32_t magic; + uint16_t version; + uint16_t header_len; + uint32_t total_len; + uint16_t model_crc; + uint16_t system_count; + uint16_t node_count; + uint16_t block_count; + uint32_t system_offset; + uint32_t node_offset; + uint32_t block_offset; + uint32_t metadata_offset; + uint16_t specification_version_len; + uint16_t product_information_len; + uint16_t container_issue_information_len; + uint16_t reserved; +} PACKED felica_sim_model_header_t; + +typedef struct { + uint16_t system_code; + uint8_t idm[8]; + uint8_t pmm[8]; + uint8_t encryption_identifier; + uint8_t reserved; + uint16_t first_node; + uint16_t node_count; +} PACKED felica_sim_system_record_t; + +typedef struct { + uint16_t system_index; + uint16_t node_code_le; + uint16_t end_code_le; + uint16_t first_block; + uint16_t block_count; + uint16_t des_key_version_le; + uint16_t aes_key_version_le; + uint8_t flags; + uint8_t reserved; +} PACKED felica_sim_node_record_t; + +typedef struct { + uint16_t node_index; + uint16_t block_number; + uint8_t data[16]; +} PACKED felica_sim_block_record_t; + //----------------------------------------------------------------------------- // FeliCa //-----------------------------------------------------------------------------