From 1b902aa01afa39ff413b74142cd56d092b4500a1 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Sun, 9 Sep 2018 16:40:20 +0200 Subject: [PATCH] Legic Tag Simulator (#666) * FPGA Hi-Simulate: Formatted code * FPGA Hi-Simulate: Fixed documantation * FPGA Hi-Simulate: Freed up 4 LUTs * FPGA Hi-Simulate: Added 212kHz SSP-Clock option * 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. * Legic: Implemented RX and TX for card simulation * Legic: Implemented setup phase for card simulation * Legic: Implemented read command for card simulation * Legic: Implemented write command for card simulation --- armsrc/Makefile | 3 +- armsrc/appmain.c | 3 +- armsrc/legicrf.c | 427 +++------------------------------------- armsrc/legicrf.h | 2 +- armsrc/legicrfsim.c | 468 ++++++++++++++++++++++++++++++++++++++++++++ armsrc/legicrfsim.h | 19 ++ client/cmdhflegic.c | 8 +- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_simulate.v | 44 ++--- 9 files changed, 539 insertions(+), 435 deletions(-) create mode 100644 armsrc/legicrfsim.c create mode 100644 armsrc/legicrfsim.h diff --git a/armsrc/Makefile b/armsrc/Makefile index d4b13c6b..046ad1bc 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -59,11 +59,12 @@ THUMBSRC = start.c \ # These are to be compiled in ARM mode ARMSRC = fpgaloader.c \ legicrf.c \ + legicrfsim.c \ + legic_prng.c \ $(SRC_ISO14443a) \ $(SRC_ISO14443b) \ $(SRC_CRAPTO1) \ $(SRC_CRC) \ - legic_prng.c \ iclass.c \ BigBuf.c \ optimized_cipher.c \ diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 4034788a..f7bcd620 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -21,6 +21,7 @@ #include "printf.h" #include "string.h" #include "legicrf.h" +#include "legicrfsim.h" #include "hitag2.h" #include "hitagS.h" #include "lfsampling.h" @@ -1090,7 +1091,7 @@ 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: diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 2a236b6f..c8a4829f 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 @@ -20,30 +20,8 @@ #include "legic.h" #include "crc.h" -static struct legic_frame { - int bits; - uint32_t data; -} current_frame; - -static enum { - STATE_DISCON, - STATE_IV, - STATE_CON, -} legic_state; - -static crc_t legic_crc; -static int legic_read_count; -static uint32_t legic_prng_bc; -static uint32_t legic_prng_iv; - -static int legic_phase_drift; -static int legic_frame_drift; -static int legic_reqresp_drift; - -AT91PS_TC timer; -AT91PS_TC prng_timer; - static legic_card_select_t card;/* metadata of currently selected card */ +static crc_t legic_crc; //----------------------------------------------------------------------------- // Frame timing and pseudorandom number generator @@ -71,11 +49,6 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define TAG_BIT_PERIOD 150 /* 100us */ #define TAG_WRITE_TIMEOUT 60 /* 40 * 100us (write should take at most 3.6ms) */ -#define SIM_DIVISOR 586 /* prng_time/DIV count prng needs to be forwared */ -#define SIM_SHIFT 900 /* prng_time+SHIFT shift of delayed start */ -#define RWD_TIME_FUZZ 20 /* rather generous 13us, since the peak detector - /+ hysteresis fuzz quite a bit */ - #define LEGIC_READ 0x01 /* Read Command */ #define LEGIC_WRITE 0x00 /* Write Command */ @@ -86,8 +59,6 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define INPUT_THRESHOLD 8 /* heuristically determined, lower values */ /* lead to detecting false ack during write */ -#define FUZZ_EQUAL(value, target, fuzz) ((value) > ((target)-(fuzz)) && (value) < ((target)+(fuzz))) - //----------------------------------------------------------------------------- // I/O interface abstraction (FPGA -> ARM) //----------------------------------------------------------------------------- @@ -125,8 +96,8 @@ static inline uint8_t rx_byte_from_fpga() { // also. // // The demedulated should be alligned to the bit periode by the caller. This is -// done in rx_bit_as_reader and rx_ack_as_reader. -static inline bool rx_bit_as_reader() { +// done in rx_bit and rx_ack. +static inline bool rx_bit() { int32_t cq = 0; int32_t ci = 0; @@ -158,7 +129,7 @@ static inline bool rx_bit_as_reader() { // be circumvented, but the adventage over bitbang would be little. //----------------------------------------------------------------------------- -static inline void tx_bit_as_reader(bool bit) { +static inline void tx_bit(bool bit) { // insert pause LOW(GPIO_SSC_DOUT); last_frame_end += RWD_TIME_PAUSE; @@ -180,7 +151,7 @@ static inline void tx_bit_as_reader(bool bit) { // present. //----------------------------------------------------------------------------- -static void tx_frame_as_reader(uint32_t frame, uint8_t len) { +static void tx_frame(uint32_t frame, uint8_t len) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); // wait for next tx timeslot @@ -190,7 +161,7 @@ static void tx_frame_as_reader(uint32_t frame, uint8_t len) { // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { bool bit = (frame >> i) & 0x01; - tx_bit_as_reader(bit ^ legic_prng_get_bit()); + tx_bit(bit ^ legic_prng_get_bit()); legic_prng_forward(1); }; @@ -201,7 +172,7 @@ static void tx_frame_as_reader(uint32_t frame, uint8_t len) { HIGH(GPIO_SSC_DOUT); } -static uint32_t rx_frame_as_reader(uint8_t len) { +static uint32_t rx_frame(uint8_t len) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ | FPGA_HF_READER_RX_XCORR_QUARTER_FREQ); @@ -211,11 +182,11 @@ static uint32_t rx_frame_as_reader(uint8_t len) { while(GET_TICKS < last_frame_end) { }; uint32_t frame = 0; - for(uint8_t i = 0; i < len; i++) { - frame |= (rx_bit_as_reader() ^ legic_prng_get_bit()) << i; + for(uint8_t i = 0; i < len; ++i) { + frame |= (rx_bit() ^ legic_prng_get_bit()) << i; legic_prng_forward(1); - // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD last_frame_end += TAG_BIT_PERIOD; while(GET_TICKS < last_frame_end) { }; } @@ -223,7 +194,7 @@ static uint32_t rx_frame_as_reader(uint8_t len) { return frame; } -static bool rx_ack_as_reader() { +static bool rx_ack() { // change fpga into rx mode FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ @@ -236,10 +207,10 @@ static bool rx_ack_as_reader() { uint32_t ack = 0; for(uint8_t i = 0; i < TAG_WRITE_TIMEOUT; ++i) { // sample bit - ack = rx_bit_as_reader(); + ack = rx_bit(); legic_prng_forward(1); - // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD last_frame_end += TAG_BIT_PERIOD; while(GET_TICKS < last_frame_end) { }; @@ -256,7 +227,7 @@ static bool rx_ack_as_reader() { // 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) { @@ -313,8 +284,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; @@ -323,24 +294,24 @@ static uint32_t setup_phase_reader(uint8_t iv) { while(GET_TICKS < last_frame_end) { }; legic_prng_init(0); - tx_frame_as_reader(iv, 7); + tx_frame(iv, 7); - // configure iv + // configure prng legic_prng_init(iv); legic_prng_forward(2); // receive card type - int32_t card_type = rx_frame_as_reader(6); + int32_t card_type = rx_frame(6); legic_prng_forward(3); // send obsfuscated acknowledgment frame switch (card_type) { case 0x0D: - tx_frame_as_reader(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 + tx_frame(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 break; case 0x1D: case 0x3D: - tx_frame_as_reader(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 + tx_frame(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 break; } @@ -359,9 +330,9 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { // read one byte LED_B_ON(); legic_prng_forward(2); - tx_frame_as_reader(cmd, cmd_sz); + tx_frame(cmd, cmd_sz); legic_prng_forward(2); - uint32_t frame = rx_frame_as_reader(12); + uint32_t frame = rx_frame(12); LED_B_OFF(); // split frame into data and crc @@ -391,12 +362,12 @@ bool write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { // send write command LED_C_ON(); legic_prng_forward(2); - tx_frame_as_reader(cmd, addr_sz + 1 + 8 + 4); // sz = addr_sz + cmd + data + crc + tx_frame(cmd, addr_sz + 1 + 8 + 4); // sz = addr_sz + cmd + data + crc legic_prng_forward(3); LED_C_OFF(); // wait for ack - return rx_ack_as_reader(); + return rx_ack(); } //----------------------------------------------------------------------------- @@ -413,7 +384,7 @@ void LegicRfReader(int offset, int bytes) { // establish shared secret and detect card type DbpString("Reading card ..."); - uint8_t card_type = setup_phase_reader(SESSION_IV); + uint8_t card_type = setup_phase(SESSION_IV); if(init_card(card_type, &card) != 0) { Dbprintf("No or unknown card found, aborting"); goto OUT; @@ -463,7 +434,7 @@ void LegicRfWriter(int bytes, int offset) { // establish shared secret and detect card type Dbprintf("Writing 0x%02.2x - 0x%02.2x ...", offset, offset+bytes); - uint8_t card_type = setup_phase_reader(SESSION_IV); + uint8_t card_type = setup_phase(SESSION_IV); if(init_card(card_type, &card) != 0) { Dbprintf("No or unknown card found, aborting"); goto OUT; @@ -492,345 +463,3 @@ OUT: LED_D_OFF(); StopTicks(); } - -//----------------------------------------------------------------------------- -// Legic Simulator -//----------------------------------------------------------------------------- - -static void setup_timer(void) -{ - /* Set up Timer 1 to use for measuring time between pulses. Since we're bit-banging - * this it won't be terribly accurate but should be good enough. - */ - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC1); - timer = AT91C_BASE_TC1; - timer->TC_CCR = AT91C_TC_CLKDIS; - timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - - /* - * Set up Timer 2 to use for measuring time between frames in - * tag simulation mode. Runs 4x faster as Timer 1 - */ - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC2); - prng_timer = AT91C_BASE_TC2; - prng_timer->TC_CCR = AT91C_TC_CLKDIS; - prng_timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV2_CLOCK; - prng_timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; -} - -/* Generate Keystream */ -static uint32_t get_key_stream(int skip, int count) -{ - uint32_t key=0; int i; - - /* Use int to enlarge timer tc to 32bit */ - legic_prng_bc += prng_timer->TC_CV; - prng_timer->TC_CCR = AT91C_TC_SWTRG; - - /* If skip == -1, forward prng time based */ - if(skip == -1) { - i = (legic_prng_bc+SIM_SHIFT)/SIM_DIVISOR; /* Calculate Cycles based on timer */ - i -= legic_prng_count(); /* substract cycles of finished frames */ - i -= count; /* substract current frame length, rewidn to bedinning */ - legic_prng_forward(i); - } else { - legic_prng_forward(skip); - } - - /* Write Time Data into LOG */ - uint8_t *BigBuf = BigBuf_get_addr(); - if(count == 6) { i = -1; } else { i = legic_read_count; } - BigBuf[OFFSET_LOG+128+i] = legic_prng_count(); - BigBuf[OFFSET_LOG+256+i*4] = (legic_prng_bc >> 0) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+1] = (legic_prng_bc >> 8) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+2] = (legic_prng_bc >>16) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+3] = (legic_prng_bc >>24) & 0xff; - BigBuf[OFFSET_LOG+384+i] = count; - - /* Generate KeyStream */ - for(i=0; iPIO_CODR = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - - /* Use time to crypt frame */ - if(crypt) { - legic_prng_forward(2); /* TAG_TIME_WAIT -> shift by 2 */ - int i; int key = 0; - for(i=0; iTC_CV < (TAG_FRAME_WAIT - 30)) ; - - int i; - for(i=0; iTC_CV + TAG_BIT_PERIOD; - int bit = response & 1; - response = response >> 1; - if(bit) { - AT91C_BASE_PIOA->PIO_SODR = GPIO_SSC_DOUT; - } else { - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - } - while(timer->TC_CV < nextbit) ; - } - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; -} - -static void frame_append_bit(struct legic_frame * const f, int bit) -{ - if(f->bits >= 31) { - return; /* Overflow, won't happen */ - } - f->data |= (bit<bits); - f->bits++; -} - -static void frame_clean(struct legic_frame * const f) -{ - f->data = 0; - f->bits = 0; -} - -/* Handle (whether to respond) a frame in tag mode */ -static void frame_handle_tag(struct legic_frame const * const f) -{ - uint8_t *BigBuf = BigBuf_get_addr(); - - /* First Part of Handshake (IV) */ - if(f->bits == 7) { - if(f->data == SESSION_IV) { - LED_C_ON(); - prng_timer->TC_CCR = AT91C_TC_SWTRG; - legic_prng_init(f->data); - frame_send_tag(0x3d, 6, 1); /* 0x3d^0x26 = 0x1b */ - legic_state = STATE_IV; - legic_read_count = 0; - legic_prng_bc = 0; - legic_prng_iv = f->data; - - /* TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - while(timer->TC_CV < 280); - return; - } else if((prng_timer->TC_CV % 50) > 40) { - legic_prng_init(f->data); - frame_send_tag(0x3d, 6, 1); - SpinDelay(20); - return; - } - } - - /* 0x19==??? */ - if(legic_state == STATE_IV) { - if((f->bits == 6) && (f->data == (0x19 ^ get_key_stream(1, 6)))) { - legic_state = STATE_CON; - - /* TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - while(timer->TC_CV < 200); - return; - } else { - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("0x19 - Frame: %03.3x", f->data); - return; - } - } - - /* Read */ - if(f->bits == 11) { - if(legic_state == STATE_CON) { - int key = get_key_stream(-1, 11); //legic_phase_drift, 11); - int addr = f->data ^ key; addr = addr >> 1; - int data = BigBuf[addr]; - int hash = calc_crc4(addr, data, 11) << 8; - BigBuf[OFFSET_LOG+legic_read_count] = (uint8_t)addr; - legic_read_count++; - - //Dbprintf("Data:%03.3x, key:%03.3x, addr: %03.3x, read_c:%u", f->data, key, addr, read_c); - legic_prng_forward(legic_reqresp_drift); - - frame_send_tag(hash | data, 12, 1); - - /* SHORT TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - legic_prng_forward(legic_frame_drift); - while(timer->TC_CV < 180); - return; - } - } - - /* Write */ - if(f->bits == 23) { - int key = get_key_stream(-1, 23); //legic_frame_drift, 23); - int addr = f->data ^ key; addr = addr >> 1; addr = addr & 0x3ff; - int data = f->data ^ key; data = data >> 11; data = data & 0xff; - - /* write command */ - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("write - addr: %x, data: %x", addr, data); - return; - } - - if(legic_state != STATE_DISCON) { - Dbprintf("Unexpected: sz:%u, Data:%03.3x, State:%u, Count:%u", f->bits, f->data, legic_state, legic_read_count); - int i; - Dbprintf("IV: %03.3x", legic_prng_iv); - for(i = 0; iPIO_ODR = GPIO_SSC_DIN; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DIN; - - setup_timer(); - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); - - int old_level = 0; - int active = 0; - legic_state = STATE_DISCON; - - LED_B_ON(); - DbpString("Starting Legic emulator, press button to end"); - while(!BUTTON_PRESS()) { - int level = !!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - int time = timer->TC_CV; - - if(level != old_level) { - if(level == 1) { - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - if(FUZZ_EQUAL(time, RWD_TIME_1, RWD_TIME_FUZZ)) { - /* 1 bit */ - emit(1); - active = 1; - LED_A_ON(); - } else if(FUZZ_EQUAL(time, RWD_TIME_0, RWD_TIME_FUZZ)) { - /* 0 bit */ - emit(0); - active = 1; - LED_A_ON(); - } else if(active) { - /* invalid */ - emit(-1); - active = 0; - LED_A_OFF(); - } - } - } - - if(time >= (RWD_TIME_1+RWD_TIME_FUZZ) && active) { - /* Frame end */ - emit(-1); - active = 0; - LED_A_OFF(); - } - - if(time >= (20*RWD_TIME_1) && (timer->TC_SR & AT91C_TC_CLKSTA)) { - timer->TC_CCR = AT91C_TC_CLKDIS; - } - - old_level = level; - WDT_HIT(); - } - DbpString("Stopped"); - LED_B_OFF(); - LED_A_OFF(); - LED_C_OFF(); -} - diff --git a/armsrc/legicrf.h b/armsrc/legicrf.h index 46459856..9f8cf457 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 @@ -11,7 +12,6 @@ #ifndef __LEGICRF_H #define __LEGICRF_H -extern void LegicRfSimulate(int phase, int frame, int reqresp); extern void LegicRfReader(int bytes, int offset); extern void LegicRfWriter(int bytes, int offset); diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c new file mode 100644 index 00000000..07a0a62d --- /dev/null +++ b/armsrc/legicrfsim.c @@ -0,0 +1,468 @@ +//----------------------------------------------------------------------------- +// (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 "proxmark3.h" +#include "apps.h" +#include "util.h" +#include "string.h" + +#include "legicrfsim.h" +#include "legic_prng.h" +#include "legic.h" +#include "crc.h" + +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_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_D_ON(); + int8_t bit = rx_bit(); + LED_D_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 +//----------------------------------------------------------------------------- + +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_addr(); + + // init crc calculator + crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); + + // start 212kHz timer (running from SSP Clock) + 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; +} + +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) { + 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; + } + + // 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; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// 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(); + + // wait for carrier, restart after timeout + if(!wait_for(RWD_PULSE, GetCountSspClk() + TAG_BIT_PERIOD)) { + continue; + } + + // 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: + DbpString("Stopped"); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_A_OFF(); + LED_C_OFF(); + LED_D_OFF(); + StopTicks(); +} diff --git a/armsrc/legicrfsim.h b/armsrc/legicrfsim.h new file mode 100644 index 00000000..c1c8a86e --- /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 691e1978..8fbd4578 100644 --- a/client/cmdhflegic.c +++ b/client/cmdhflegic.c @@ -28,7 +28,7 @@ static command_t CommandTable[] = {"reader", CmdLegicRFRead, 0, "[offset [length]] -- read bytes from a LEGIC card"}, {"save", CmdLegicSave, 0, " [] -- Store samples"}, {"load", CmdLegicLoad, 0, " -- Restore samples"}, - {"sim", CmdLegicRfSim, 0, "[phase drift [frame drift [req/resp drift]]] Start tag simulator (use after load or read)"}, + {"sim", CmdLegicRfSim, 0, "[tagtype, 0:MIM22, 1:MIM256, 2:MIM1024] Start tag simulator (use after load or read)"}, {"write", CmdLegicRfWrite,0, " -- Write sample buffer (user after load or read)"}, {"fill", CmdLegicRfFill, 0, " -- Fill/Write tag with constant value"}, {NULL, NULL, 0, NULL} @@ -320,10 +320,8 @@ int CmdLegicSave(const char *Cmd) int CmdLegicRfSim(const char *Cmd) { UsbCommand c={CMD_SIMULATE_TAG_LEGIC_RF}; - c.arg[0] = 6; - c.arg[1] = 3; - c.arg[2] = 0; - sscanf(Cmd, " %" SCNi64 " %" SCNi64 " %" SCNi64, &c.arg[0], &c.arg[1], &c.arg[2]); + c.arg[0] = 1; + sscanf(Cmd, " %" SCNi64, &c.arg[0]); SendCommand(&c); return 0; } diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 939ba93a54ce00e9425a1156d3900f1af58c4c8e..179e87eed22bc326f37e33f6925116916701c6bf 100644 GIT binary patch literal 42175 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 literal 42175 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 diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 0768c29d..78650c4a 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -51,38 +51,29 @@ 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) 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 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 if(mod_type == 3'b010) + // Get next bit at 212kHz + ssp_clk <= ssp_clk_divider[5]; 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 -//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,19 +87,19 @@ 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; 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) @@ -116,9 +107,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 @@ -133,9 +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; -//reg dbg; -//always @(ssp_dout) -// dbg <= ssp_dout; +assign dbg = ssp_din; endmodule -- 2.39.2