mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-16 14:52:59 +00:00
Merge pull request #3376 from kormax/felica-sim
Add `hf felica sim` command
This commit is contained in:
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
+47
-97
@@ -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?!?
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
+882
-65
@@ -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_("<Enter>") " 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", "<fn>", "JSON dump file"),
|
||||
arg_str0(NULL, "read-without-encryption-error-location-indication",
|
||||
"<mask|index|flag>", "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, "<UID> -- Simulate ISO 18092/FeliCa tag"}
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -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" },
|
||||
|
||||
+145
@@ -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
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user