From 6ca899d130f4dcf2f8159d23ac5df90f20503d17 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:18 +0200 Subject: [PATCH 01/10] FPGA Hi-Simulate: Formatted code --- fpga/hi_simulate.v | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 0768c29de..3976de93d 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -59,7 +59,7 @@ always @(posedge adc_clk) ssp_clk_divider <= (ssp_clk_divider + 1); reg ssp_clk; -reg ssp_frame; + always @(negedge adc_clk) begin //If we're in 101, we only need a new bit every 8th carrier bit (53Hz). Otherwise, get next bit at 424Khz @@ -81,8 +81,6 @@ begin end -//assign ssp_clk = ssp_clk_divider[4]; - // Divide SSP_CLK by 8 to produce the byte framing signal; the phase of // this is arbitrary, because it's just a bitstream. // One nasty issue, though: I can't make it work with both rx and tx at @@ -96,12 +94,12 @@ always @(negedge ssp_clk) ssp_frame_divider_from_arm <= (ssp_frame_divider_from_arm + 1); - +reg ssp_frame; always @(ssp_frame_divider_to_arm or ssp_frame_divider_from_arm or mod_type) if(mod_type == 3'b000) // not modulating, so listening, to ARM ssp_frame = (ssp_frame_divider_to_arm == 3'b000); else - ssp_frame = (ssp_frame_divider_from_arm == 3'b000); + ssp_frame = (ssp_frame_divider_from_arm == 3'b000); // Synchronize up the after-hysteresis signal, to produce DIN. reg ssp_din; @@ -116,9 +114,9 @@ always @(mod_type or ssp_clk or ssp_dout) else if(mod_type == 3'b001) modulating_carrier <= ssp_dout ^ ssp_clk_divider[3]; // XOR means BPSK else if(mod_type == 3'b010) - modulating_carrier <= ssp_dout & ssp_clk_divider[5]; // switch 212kHz subcarrier on/off + modulating_carrier <= ssp_dout & ssp_clk_divider[5]; // switch 212kHz subcarrier on/off else if(mod_type == 3'b100 || mod_type == 3'b101) - modulating_carrier <= ssp_dout & ssp_clk_divider[4]; // switch 424kHz modulation on/off + modulating_carrier <= ssp_dout & ssp_clk_divider[4]; // switch 424kHz modulation on/off else modulating_carrier <= 1'b0; // yet unused @@ -134,8 +132,5 @@ assign pwr_oe4 = modulating_carrier; assign pwr_oe3 = 1'b0; assign dbg = modulating_carrier; -//reg dbg; -//always @(ssp_dout) -// dbg <= ssp_dout; endmodule From cef5dc4e838675ef8b69265d39845e68a127009a Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:20 +0200 Subject: [PATCH 02/10] FPGA Hi-Simulate: Fixed documantation --- fpga/hi_simulate.v | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 3976de93d..65b61d6aa 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -51,8 +51,8 @@ begin end -// Divide 13.56 MHz by 32 to produce the SSP_CLK -// The register is bigger to allow higher division factors of up to /128 +// Divide 13.56 MHz to produce various frequencies for SSP_CLK +// and modulation. 11 bits allow for factors of up to /128. reg [10:0] ssp_clk_divider; always @(posedge adc_clk) @@ -106,7 +106,7 @@ reg ssp_din; always @(posedge ssp_clk) ssp_din = after_hysteresis; -// Modulating carrier frequency is fc/16, reuse ssp_clk divider for that +// Modulating carrier frequency is fc/64 (212kHz) to fc/16 (848kHz). Reuse ssp_clk divider for that. reg modulating_carrier; always @(mod_type or ssp_clk or ssp_dout) if(mod_type == 3'b000) From 0994c91888bceb7f407d57d713f4b4e50e1fe9a5 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:22 +0200 Subject: [PATCH 03/10] FPGA Hi-Simulate: Freed up 4 LUTs --- fpga/hi_simulate.v | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 65b61d6aa..92445edb6 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -62,22 +62,12 @@ reg ssp_clk; always @(negedge adc_clk) begin - //If we're in 101, we only need a new bit every 8th carrier bit (53Hz). Otherwise, get next bit at 424Khz if(mod_type == 3'b101) - begin - if(ssp_clk_divider[7:0] == 8'b00000000) - ssp_clk <= 1'b0; - if(ssp_clk_divider[7:0] == 8'b10000000) - ssp_clk <= 1'b1; - - end + // Get bit every at 53KHz (every 8th carrier bit of 424kHz) + ssp_clk <= ssp_clk_divider[7]; else - begin - if(ssp_clk_divider[4:0] == 5'd0)//[4:0] == 5'b00000) - ssp_clk <= 1'b1; - if(ssp_clk_divider[4:0] == 5'd16) //[4:0] == 5'b10000) - ssp_clk <= 1'b0; - end + // Get next bit at 424Khz + ssp_clk <= ssp_clk_divider[4] end From e472a21194b407fc201b9e310c74f57f32238486 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:23 +0200 Subject: [PATCH 04/10] FPGA Hi-Simulate: Added 212kHz SSP-Clock option --- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_simulate.v | 7 +++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 00e71c432289cab51d023861a43828767f15d241..7e3fc14e2223e60a37b3427515e38c4d3dd62f4b 100644 GIT binary patch literal 42175 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 SDJOpiTT13LR@j#EGx-nlfX!L} diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 92445edb6..78650c4a9 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -65,9 +65,12 @@ begin if(mod_type == 3'b101) // Get bit every at 53KHz (every 8th carrier bit of 424kHz) ssp_clk <= ssp_clk_divider[7]; + else if(mod_type == 3'b010) + // Get next bit at 212kHz + ssp_clk <= ssp_clk_divider[5]; else // Get next bit at 424Khz - ssp_clk <= ssp_clk_divider[4] + ssp_clk <= ssp_clk_divider[4]; end @@ -121,6 +124,6 @@ assign pwr_oe4 = modulating_carrier; // This one is always on, so that we can watch the carrier. assign pwr_oe3 = 1'b0; -assign dbg = modulating_carrier; +assign dbg = ssp_din; endmodule From 61e4eac2b249e8a4d4b86fe6ea2089c22e87b49f Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:25 +0200 Subject: [PATCH 05/10] Legic: Moved card simulator into separate file & cleaned interface. Reader and card simulation have almost no common code. Moreover the sim uses an SSP Clock at 212kHz for all timings to prevent any drifting from the PRNG. This clock speed is not available in reader simulation mode (SSP runs at up to 3.4MHz, and changes speed between TX and RX). For these reasons having the code in separate files makes it significantly cleaner. --- armsrc/Makefile | 2 +- armsrc/appmain.c | 5 +- armsrc/legicrf.c | 14 ++--- armsrc/legicrf.h | 2 +- armsrc/legicrfsim.c | 139 ++++++++++++++++++++++++++++++++++++++++++++ armsrc/legicrfsim.h | 19 ++++++ client/cmdhflegic.c | 19 +++--- 7 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 armsrc/legicrfsim.c create mode 100644 armsrc/legicrfsim.h diff --git a/armsrc/Makefile b/armsrc/Makefile index d70008e0c..aedcd79a1 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -54,7 +54,7 @@ SRC_FELICA = felica.c SRC_CRAPTO1 = crypto1.c des.c aes.c desfire_key.c desfire_crypto.c mifaredesfire.c SRC_CRC = crc.c crc16.c crc32.c SRC_ICLASS = iclass.c optimized_cipher.c -SRC_LEGIC = legicrf.c legic_prng.c +SRC_LEGIC = legicrf.c legicrfsim.c legic_prng.c SRC_BEE = bee.c # RDV40 related hardware support diff --git a/armsrc/appmain.c b/armsrc/appmain.c index c6e6e0dee..a048418d0 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -19,6 +19,7 @@ #include "printf.h" #include "string.h" #include "legicrf.h" +#include "legicrfsim.h" #include "lfsampling.h" #include "BigBuf.h" #include "mifareutil.h" @@ -796,10 +797,10 @@ void UsbPacketReceived(uint8_t *packet, int len) { #ifdef WITH_LEGICRF case CMD_SIMULATE_TAG_LEGIC_RF: - LegicRfSimulate(c->arg[0], c->arg[1], c->arg[2]); + LegicRfSimulate(c->arg[0]); break; case CMD_WRITER_LEGIC_RF: - LegicRfWriter( c->arg[0], c->arg[1], c->arg[2], c->d.asBytes); + LegicRfWriter(c->arg[0], c->arg[1], c->arg[2], c->d.asBytes); break; case CMD_READER_LEGIC_RF: LegicRfReader(c->arg[0], c->arg[1], c->arg[2]); diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 3a45c4c67..59cda1221 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Plötz // 2016 Iceman -// 2018 AntiCat (rwd rewritten) +// 2018 AntiCat // // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of @@ -16,7 +16,7 @@ #include "legic_prng.h" /* legic PRNG impl */ #include "legic.h" /* legic_card_select_t struct */ -static uint8_t* legic_mem; /* card memory, used for read, write and sim */ +static uint8_t* legic_mem; /* card memory, used for read, write */ static legic_card_select_t card;/* metadata of currently selected card */ static crc_t legic_crc; @@ -179,7 +179,7 @@ static uint32_t rx_frame(uint8_t len) { uint32_t last_frame_start = last_frame_end; uint32_t frame = 0; - for(uint8_t i = 0; i < len; i++) { + for(uint8_t i = 0; i < len; ++i) { frame |= (rx_bit() ^ legic_prng_get_bit()) << i; legic_prng_forward(1); @@ -235,7 +235,7 @@ static bool rx_ack() { // Legic Reader //----------------------------------------------------------------------------- -int init_card(uint8_t cardtype, legic_card_select_t *p_card) { +static int init_card(uint8_t cardtype, legic_card_select_t *p_card) { p_card->tagtype = cardtype; switch(p_card->tagtype) { @@ -314,7 +314,7 @@ static uint32_t setup_phase_reader(uint8_t iv) { legic_prng_init(0); tx_frame(iv, 7); - // configure iv + // configure prng legic_prng_init(iv); legic_prng_forward(2); @@ -501,7 +501,3 @@ OUT: switch_off(); StopTicks(); } - -void LegicRfSimulate(int phase, int frame, int reqresp) { - cmd_send(CMD_ACK, 0, 0, 0, 0, 0); //TODO Implement -} diff --git a/armsrc/legicrf.h b/armsrc/legicrf.h index 4bcf04899..639687558 100644 --- a/armsrc/legicrf.h +++ b/armsrc/legicrf.h @@ -1,5 +1,6 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Plötz +// 2018 AntiCat // // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of @@ -16,6 +17,5 @@ extern void LegicRfInfo(void); extern void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv); extern void LegicRfWriter(uint16_t offset, uint16_t byte, uint8_t iv, uint8_t *data); -extern void LegicRfSimulate(int phase, int frame, int reqresp); #endif /* __LEGICRF_H */ diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c new file mode 100644 index 000000000..791082d6b --- /dev/null +++ b/armsrc/legicrfsim.c @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// (c) 2009 Henryk Plötz +// 2016 Iceman +// 2018 AntiCat +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// LEGIC RF simulation code +//----------------------------------------------------------------------------- +#include "legicrf.h" + +#include "ticks.h" /* timers */ +#include "crc.h" /* legic crc-4 */ +#include "legic_prng.h" /* legic PRNG impl */ +#include "legic.h" /* legic_card_select_t struct */ + +static uint8_t* legic_mem; /* card memory, used for sim */ +static legic_card_select_t card;/* metadata of currently selected card */ +static crc_t legic_crc; + +//----------------------------------------------------------------------------- +// Frame timing and pseudorandom number generator +// +// The Prng is forwarded every 99.1us (TAG_BIT_PERIOD), except when the reader is +// transmitting. In that case the prng has to be forwarded every bit transmitted: +// - 31.3us for a 0 (RWD_TIME_0) +// - 99.1us for a 1 (RWD_TIME_1) +// +// The data dependent timing makes writing comprehensible code significantly +// harder. The current aproach forwards the prng data based if there is data on +// air and time based, using GetCountSspClk(), during computational and wait +// periodes. SSP Clock is clocked by the FPGA at 212 kHz (subcarrier frequency). +// +// To not have the necessity to calculate/guess exection time dependend timeouts +// tx_frame and rx_frame use a shared timestamp to coordinate tx and rx timeslots. +//----------------------------------------------------------------------------- + +static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ + +#define TAG_FRAME_WAIT 70 /* 330us from READER frame end to TAG frame start */ +#define TAG_BIT_PERIOD 21 /* 99.1us */ + +#define RWD_TIME_PAUSE 4 /* 18.9us */ +#define RWD_TIME_1 21 /* RWD_TIME_PAUSE 18.9us off + 80.2us on = 99.1us */ +#define RWD_TIME_0 13 /* RWD_TIME_PAUSE 18.9us off + 42.4us on = 61.3us */ +#define RWD_CMD_TIMEOUT 40 /* 40 * 99.1us (arbitrary value) */ + +//----------------------------------------------------------------------------- +// Legic Simulator +//----------------------------------------------------------------------------- + +static int32_t init_card(uint8_t cardtype, legic_card_select_t *p_card) { + p_card->tagtype = cardtype; + + switch(p_card->tagtype) { + case 0: + p_card->cmdsize = 6; + p_card->addrsize = 5; + p_card->cardsize = 22; + break; + case 1: + p_card->cmdsize = 9; + p_card->addrsize = 8; + p_card->cardsize = 256; + break; + case 2: + p_card->cmdsize = 11; + p_card->addrsize = 10; + p_card->cardsize = 1024; + break; + default: + p_card->cmdsize = 0; + p_card->addrsize = 0; + p_card->cardsize = 0; + return 2; + } + return 0; +} + +static void init_tag() { + // configure FPGA + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR + | FPGA_HF_SIMULATOR_MODULATE_212K); + SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + + // configure SSC with defaults + FpgaSetupSsc(); + + // first pull output to low to prevent glitches then re-claim GPIO_SSC_DOUT + LOW(GPIO_SSC_DOUT); + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + + // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. + legic_mem = BigBuf_get_EM_addr(); + + // init crc calculator + crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); + + // start 212kHz timer (running from SSP Clock) + StartCountSspClk(); +} + +//----------------------------------------------------------------------------- +// Command Line Interface +// +// Only this function is public / called from appmain.c +//----------------------------------------------------------------------------- + +void LegicRfSimulate(uint8_t cardtype) { + // configure ARM and FPGA + init_tag(); + + // verify command line input + if(init_card(cardtype, &card) != 0) { + DbpString("Unknown tagtype."); + goto OUT; + } + + LED_A_ON(); + DbpString("Starting Legic emulator, press button to end"); + while(!BUTTON_PRESS()) { + WDT_HIT(); + + // init coordination timestamp + last_frame_end = GetCountSspClk(); + + // reset prng + legic_prng_init(0); + } + +OUT: + DbpString("Stopped"); + switch_off(); + StopTicks(); +} diff --git a/armsrc/legicrfsim.h b/armsrc/legicrfsim.h new file mode 100644 index 000000000..c1c8a86e2 --- /dev/null +++ b/armsrc/legicrfsim.h @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// (c) 2009 Henryk Plötz +// 2018 AntiCat +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// LEGIC RF emulation public interface +//----------------------------------------------------------------------------- + +#ifndef __LEGICRFSIM_H +#define __LEGICRFSIM_H + +#include "proxmark3.h" + +extern void LegicRfSimulate(uint8_t tagtype); + +#endif /* __LEGICRFSIM_H */ diff --git a/client/cmdhflegic.c b/client/cmdhflegic.c index 559209afb..0b744c659 100644 --- a/client/cmdhflegic.c +++ b/client/cmdhflegic.c @@ -46,19 +46,15 @@ int usage_legic_rdmem(void){ int usage_legic_sim(void){ PrintAndLogEx(NORMAL, "Simulates a LEGIC Prime tag. MIM22, MIM256, MIM1024 types can be emulated"); PrintAndLogEx(NORMAL, "Use eload/esave to upload a dump into emulator memory"); - PrintAndLogEx(NORMAL, "Usage: hf legic sim [h] "); + PrintAndLogEx(NORMAL, "Usage: hf legic sim [h] "); PrintAndLogEx(NORMAL, "Options:"); PrintAndLogEx(NORMAL, " h : this help"); PrintAndLogEx(NORMAL, " : 0 = MIM22"); PrintAndLogEx(NORMAL, " : 1 = MIM256 (default)"); - PrintAndLogEx(NORMAL, " : 2 = MIM1024"); - PrintAndLogEx(NORMAL, " : phase drift"); - PrintAndLogEx(NORMAL, " : frame drift"); - PrintAndLogEx(NORMAL, " : reqresp drift"); + PrintAndLogEx(NORMAL, " : 2 = MIM1024"); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(NORMAL, "Examples:"); PrintAndLogEx(NORMAL, " hf legic sim"); - PrintAndLogEx(NORMAL, " hf legic sim "); return 0; } int usage_legic_write(void){ @@ -504,12 +500,13 @@ int CmdLegicRdmem(const char *Cmd) { return status; } -// should say which tagtype -// should load a tag to device mem. -// int phase, int frame, int reqresp int CmdLegicRfSim(const char *Cmd) { - UsbCommand c = {CMD_SIMULATE_TAG_LEGIC_RF, {6,3,0}}; - sscanf(Cmd, " %" SCNi64 " %" SCNi64 " %" SCNi64 , &c.arg[0], &c.arg[1], &c.arg[2]); + + char cmdp = param_getchar(Cmd, 0); + if ( cmdp == 'H' || cmdp == 'h' ) return usage_legic_sim(); + + UsbCommand c = {CMD_SIMULATE_TAG_LEGIC_RF, {1}}; + sscanf(Cmd, " %" SCNi64, &c.arg[0]); clearCommandBuffer(); SendCommand(&c); return 0; From fe91a3f52f2f4fc68a8578a05d9a4ce0ce86da4d Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:26 +0200 Subject: [PATCH 06/10] Legic: Implemented RX and TX for card simulation --- armsrc/legicrfsim.c | 192 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 791082d6b..862078bf7 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -40,12 +40,204 @@ static crc_t legic_crc; static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define TAG_FRAME_WAIT 70 /* 330us from READER frame end to TAG frame start */ +#define TAG_ACK_WAIT 758 /* 3.57ms from READER frame end to TAG write ACK */ #define TAG_BIT_PERIOD 21 /* 99.1us */ #define RWD_TIME_PAUSE 4 /* 18.9us */ #define RWD_TIME_1 21 /* RWD_TIME_PAUSE 18.9us off + 80.2us on = 99.1us */ #define RWD_TIME_0 13 /* RWD_TIME_PAUSE 18.9us off + 42.4us on = 61.3us */ #define RWD_CMD_TIMEOUT 40 /* 40 * 99.1us (arbitrary value) */ +#define RWD_MIN_FRAME_LEN 6 /* Shortest frame is 6 bits */ +#define RWD_MAX_FRAME_LEN 23 /* Longest frame is 23 bits */ + +#define RWD_PULSE 1 /* Pulse is signaled with GPIO_SSC_DIN high */ +#define RWD_PAUSE 0 /* Pause is signaled with GPIO_SSC_DIN low */ + +//----------------------------------------------------------------------------- +// Demodulation +//----------------------------------------------------------------------------- + +// Returns true if a pulse/pause is received within timeout +static inline bool wait_for(bool value, const uint32_t timeout) { + while((bool)(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN) != value) { + if(GetCountSspClk() > timeout) { + return false; + } + } + return true; +} + +// Returns a demedulated bit or -1 on code violation +// +// rx_bit decodes bits using a thresholds. rx_bit has to be called by as soon as +// a frame starts (first pause is received). rx_bit checks for a pause up to +// 18.9us followed by a pulse of 80.2us or 42.4us: +// - A bit length <18.9us is a code violation +// - A bit length >80.2us is a 1 +// - A bit length <80.2us is a 0 +// - A bit length >148.6us is a code violation +static inline int8_t rx_bit() { + // backup ts for threshold calculation + uint32_t bit_start = last_frame_end; + + // wait for pause to end + if(!wait_for(RWD_PULSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // wait for next pause + if(!wait_for(RWD_PAUSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // update bit and frame end + last_frame_end = GetCountSspClk(); + + // check for code violation (bit to short) + if(last_frame_end - bit_start < RWD_TIME_PAUSE) { + return -1; + } + + // apply threshold (average of RWD_TIME_0 and ) + return (last_frame_end - bit_start > (RWD_TIME_0 + RWD_TIME_1) / 2); +} + +//----------------------------------------------------------------------------- +// Modulation +// +// LEGIC RF uses a very basic load modulation from card to reader: +// - Subcarrier on for a 1 +// - Subcarrier off for for a 0 +// +// The 212kHz subcarrier is generated by the FPGA as well as a mathcing ssp clk. +// Each bit is transfered in a 99.1us slot and the first timeslot starts 330us +// after the final 20us pause generated by the reader. +//----------------------------------------------------------------------------- + +// Transmits a bit +// +// Note: The Subcarrier is not disabled during bits to prevent glitches. This is +// not mandatory but results in a cleaner signal. tx_frame will disable +// the subcarrier when the frame is done. +static inline void tx_bit(bool bit) { + LED_C_ON(); + + if(bit) { + // modulate subcarrier + HIGH(GPIO_SSC_DOUT); + } else { + // do not modulate subcarrier + LOW(GPIO_SSC_DOUT); + } + + // wait for tx timeslot to end + last_frame_end += TAG_BIT_PERIOD; + while(GetCountSspClk() < last_frame_end) { }; + LED_C_OFF(); +} + +//----------------------------------------------------------------------------- +// Frame Handling +// +// The LEGIC RF protocol from reader to card does not include explicit frame +// start/stop information or length information. The tag detects end of frame +// trough an extended pulse (>99.1us) without a pause. +// In reverse direction (card to reader) the number of bites is well known +// and depends only the command received (IV, ACK, READ or WRITE). +//----------------------------------------------------------------------------- + +static void tx_frame(uint32_t frame, uint8_t len) { + // wait for next tx timeslot + last_frame_end += TAG_FRAME_WAIT; + legic_prng_forward(TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit frame, MSB first + for(uint8_t i = 0; i < len; ++i) { + bool bit = (frame >> i) & 0x01; + tx_bit(bit ^ legic_prng_get_bit()); + legic_prng_forward(1); + }; + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +static void tx_ack() { + // wait for ack timeslot + last_frame_end += TAG_ACK_WAIT; + legic_prng_forward(TAG_ACK_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit ack (ack is not encrypted) + tx_bit(true); + legic_prng_forward(1); + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +// Returns a demedulated frame or -1 on code violation +// +// Since TX to RX delay is arbitrary rx_frame has to: +// - detect start of frame (first pause) +// - forward prng based on ts/TAG_BIT_PERIOD +// - receive the frame +// - detect end of frame (last pause) +static int32_t rx_frame(uint8_t *len) { + int32_t frame = 0; + + // add 2 SSP clock cycles (1 for tx and 1 for rx pipeline delay) + // those will be substracted at the end of the rx phase + last_frame_end -= 2; + + // wait for first pause (start of frame) + for(uint8_t i = 0; true; ++i) { + // increment prng every TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + legic_prng_forward(1); + + // if start of frame was received exit delay loop + if(wait_for(RWD_PAUSE, last_frame_end)) { + last_frame_end = GetCountSspClk(); + break; + } + + // check for code violation + if(i > RWD_CMD_TIMEOUT) { + return -1; + } + } + + // receive frame + for(*len = 0; true; ++(*len)) { + // receive next bit + LED_B_ON(); + int8_t bit = rx_bit(); + LED_B_OFF(); + + // check for code violation and to short / long frame + if((bit < 0) && ((*len < RWD_MIN_FRAME_LEN) || (*len > RWD_MAX_FRAME_LEN))) { + return -1; + } + + // check for code violation caused by end of frame + if(bit < 0) { + break; + } + + // append bit + frame |= (bit ^ legic_prng_get_bit()) << (*len); + legic_prng_forward(1); + } + + // rx_bit sets coordination timestamp to start of pause, append pause duration + // and substract 2 SSP clock cycles (1 for rx and 1 for tx pipeline delay) to + // obtain exact end of frame. + last_frame_end += RWD_TIME_PAUSE - 2; + + return frame; +} //----------------------------------------------------------------------------- // Legic Simulator From 2c6c4e5bc64383dd511e70a24501f2b7e0bdb436 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:28 +0200 Subject: [PATCH 07/10] Legic: Implemented trace log --- armsrc/legicrfsim.c | 25 +++++++++++++++++++++++++ client/cmdtrace.c | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 862078bf7..d73f09857 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -152,6 +152,9 @@ static void tx_frame(uint32_t frame, uint8_t len) { legic_prng_forward(TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1); while(GetCountSspClk() < last_frame_end) { }; + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { bool bit = (frame >> i) & 0x01; @@ -161,6 +164,10 @@ static void tx_frame(uint32_t frame, uint8_t len) { // disable subcarrier LOW(GPIO_SSC_DOUT); + + // log + uint8_t cmdbytes[] = {len, BYTEx(frame, 0), BYTEx(frame, 1)}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, false); } static void tx_ack() { @@ -169,12 +176,19 @@ static void tx_ack() { legic_prng_forward(TAG_ACK_WAIT/TAG_BIT_PERIOD - 1); while(GetCountSspClk() < last_frame_end) { }; + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + // transmit ack (ack is not encrypted) tx_bit(true); legic_prng_forward(1); // disable subcarrier LOW(GPIO_SSC_DOUT); + + // log + uint8_t cmdbytes[] = {1, 1}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, false); } // Returns a demedulated frame or -1 on code violation @@ -209,6 +223,9 @@ static int32_t rx_frame(uint8_t *len) { } } + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + // receive frame for(*len = 0; true; ++(*len)) { // receive next bit @@ -236,6 +253,10 @@ static int32_t rx_frame(uint8_t *len) { // obtain exact end of frame. last_frame_end += RWD_TIME_PAUSE - 2; + // log + uint8_t cmdbytes[] = {*len, BYTEx(frame, 0), BYTEx(frame, 1), BYTEx(frame, 2)}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, true); + return frame; } @@ -289,6 +310,10 @@ static void init_tag() { // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. legic_mem = BigBuf_get_EM_addr(); + // start trace + clear_trace(); + set_tracing(true); + // init crc calculator crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); diff --git a/client/cmdtrace.c b/client/cmdtrace.c index b30f7898a..537786bb4 100644 --- a/client/cmdtrace.c +++ b/client/cmdtrace.c @@ -521,7 +521,8 @@ int CmdTraceList(const char *Cmd) { if ( protocol == ICLASS ) PrintAndLogEx(NORMAL, "iClass - Timings are not as accurate"); if ( protocol == LEGIC ) - PrintAndLogEx(NORMAL, "LEGIC - Timings are in ticks (1us == 1.5ticks)"); + PrintAndLogEx(NORMAL, "LEGIC - Reader Mode: Timings are in ticks (1us == 1.5ticks)\n" + " Tag Mode: Timings are in sub carrier periods (1/212 kHz == 4.7us)"); if ( protocol == ISO_15693 ) PrintAndLogEx(NORMAL, "ISO15693 - Timings are not as accurate"); if ( protocol == FELICA ) From cd78b008156e4d1fe084c16757bd5b15036fd49f Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:35 +0200 Subject: [PATCH 08/10] Legic: Implemented setup phase for card simulation --- armsrc/legicrf.c | 10 ++--- armsrc/legicrfsim.c | 89 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 59cda1221..00d284926 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -302,8 +302,8 @@ static void init_reader(bool clear_mem) { // The setup consists of a three way handshake: // - Transmit initialisation vector 7 bits // - Receive card type 6 bits -// - Acknowledge frame 6 bits -static uint32_t setup_phase_reader(uint8_t iv) { +// - Transmit Acknowledge 6 bits +static uint32_t setup_phase(uint8_t iv) { // init coordination timestamp last_frame_end = GET_TICKS; @@ -398,7 +398,7 @@ void LegicRfInfo(void) { init_reader(false); // establish shared secret and detect card type - uint8_t card_type = setup_phase_reader(0x01); + uint8_t card_type = setup_phase(0x01); if(init_card(card_type, &card) != 0) { cmd_send(CMD_ACK, 0, 0, 0, 0, 0); goto OUT; @@ -435,7 +435,7 @@ void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv) { init_reader(false); // establish shared secret and detect card type - uint8_t card_type = setup_phase_reader(iv); + uint8_t card_type = setup_phase(iv); if(init_card(card_type, &card) != 0) { cmd_send(CMD_ACK, 0, 0, 0, 0, 0); goto OUT; @@ -474,7 +474,7 @@ void LegicRfWriter(uint16_t offset, uint16_t len, uint8_t iv, uint8_t *data) { } // establish shared secret and detect card type - uint8_t card_type = setup_phase_reader(iv); + uint8_t card_type = setup_phase(iv); if(init_card(card_type, &card) != 0) { cmd_send(CMD_ACK, 0, 0, 0, 0, 0); goto OUT; diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index d73f09857..195aa2738 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -321,6 +321,78 @@ static void init_tag() { StartCountSspClk(); } +// Setup reader to card connection +// +// The setup consists of a three way handshake: +// - Receive initialisation vector 7 bits +// - Transmit card type 6 bits +// - Receive Acknowledge 6 bits +static int32_t setup_phase(legic_card_select_t *p_card) { + uint8_t len = 0; + + // init coordination timestamp + last_frame_end = GetCountSspClk(); + + // reset prng + legic_prng_init(0); + + // wait for iv + int32_t iv = rx_frame(&len); + if((len != 7) || (iv < 0)) { + return -1; + } + + // configure prng + legic_prng_init(iv); + + // reply with card type + switch(p_card->tagtype) { + case 0: + tx_frame(0x0D, 6); + break; + case 1: + tx_frame(0x1D, 6); + break; + case 2: + tx_frame(0x3D, 6); + break; + } + + // wait for ack + int32_t ack = rx_frame(&len); + if((len != 6) || (ack < 0)) { + return -1; + } + + // validate data + switch(p_card->tagtype) { + case 0: + if(ack != 0x19) return -1; + break; + case 1: + if(ack != 0x39) return -1; + break; + case 2: + if(ack != 0x39) return -1; + break; + } + + // During rx the prng is clocked using the variable reader period. + // Since rx_frame detects end of frame by detecting a code violation, + // the prng is off by one bit period after each rx phase. Hence, tx + // code advances the prng by (TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1). + // This is not possible for back to back rx, so this quirk reduces + // the gap by one period. + last_frame_end += TAG_BIT_PERIOD; + + return 0; +} + +// TODO Commands are left as an exercise to the reader +static int32_t connected_phase(legic_card_select_t *p_card) { + return 0; +} + //----------------------------------------------------------------------------- // Command Line Interface // @@ -342,11 +414,20 @@ void LegicRfSimulate(uint8_t cardtype) { while(!BUTTON_PRESS()) { WDT_HIT(); - // init coordination timestamp - last_frame_end = GetCountSspClk(); + // wait for carrier, restart after timeout + if(!wait_for(RWD_PULSE, GetCountSspClk() + TAG_BIT_PERIOD)) { + continue; + } - // reset prng - legic_prng_init(0); + // wait for connection, restart on error + if(setup_phase(&card)) { + continue; + } + + // conection is established, process commands until one fails + while(!connected_phase(&card)) { + WDT_HIT(); + } } OUT: From 2981fe7ce8db62eaf836daf2136c614105065839 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:37 +0200 Subject: [PATCH 09/10] Legic: Implemented read command for card simulation --- armsrc/legicrfsim.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 195aa2738..641907741 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -388,9 +388,33 @@ static int32_t setup_phase(legic_card_select_t *p_card) { return 0; } -// TODO Commands are left as an exercise to the reader +static uint8_t calc_crc4(uint16_t cmd, uint8_t cmd_sz, uint8_t value) { + crc_clear(&legic_crc); + crc_update(&legic_crc, (value << cmd_sz) | cmd, 8 + cmd_sz); + return crc_finish(&legic_crc); +} + static int32_t connected_phase(legic_card_select_t *p_card) { - return 0; + uint8_t len = 0; + + // wait for command + int32_t cmd = rx_frame(&len); + if(cmd < 0) { + return -1; + } + + // check if command is LEGIC_READ + if(len == p_card->cmdsize) { + // prepare data + uint8_t byte = legic_mem[cmd >> 1]; + uint8_t crc = calc_crc4(cmd, p_card->cmdsize, byte); + + // transmit data + tx_frame((crc << 8) | byte, 12); + return 0; + } + + return -1; } //----------------------------------------------------------------------------- From e1fa1e659aef8d4537748db2e5f0f0182ab91168 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Wed, 5 Sep 2018 22:23:44 +0200 Subject: [PATCH 10/10] Legic: Implemented write command for card simulation --- armsrc/legicrfsim.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 641907741..71c747115 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -411,6 +411,31 @@ static int32_t connected_phase(legic_card_select_t *p_card) { // transmit data tx_frame((crc << 8) | byte, 12); + + return 0; + } + + // check if command is LEGIC_WRITE + if(len == p_card->cmdsize + 8 + 4) { + // decode data + uint16_t mask = (1 << p_card->addrsize) - 1; + uint16_t addr = (cmd >> 1) & mask; + uint8_t byte = (cmd >> p_card->cmdsize) & 0xff; + uint8_t crc = (cmd >> (p_card->cmdsize + 8)) & 0xf; + + // check received against calculated crc + uint8_t calc_crc = calc_crc4(addr << 1, p_card->cmdsize, byte); + if(calc_crc != crc) { + Dbprintf("!!! crc mismatch: %x != %x !!!", calc_crc, crc); + return -1; + } + + // store data + legic_mem[addr] = byte; + + // transmit ack + tx_ack(); + return 0; }