From 315e18e66cea20bd426be9b05337f53c9055e0c7 Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Wed, 15 Aug 2018 14:03:20 +0200 Subject: [PATCH 1/4] ISO15693 device side improvements (#652) * ISO15693 device side improvements * increase accuracy by doubling the sample frequency (hi_read_rx_xcorr.v) * adjust armsrc/iso15693.c and client/cmdhf15.c accordingly * use more accurate approximation for sqrt(ci^2 + cq^2) * improve EOF detection (was often mistaken for Logic0, resulting in "error, uneven octet! (extra bits!)") * hi_read_r_xcorr.v: avoid overflows during accumulation and truncation * explicitely cast unsigned ADC samples to signed --- armsrc/iso15693.c | 126 +++++++++++++++++++-------------------- client/cmdhf15.c | 49 ++++++++------- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_read_rx_xcorr.v | 129 ++++++++++++++++++++++++++-------------- 4 files changed, 174 insertions(+), 130 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 1f0b8193..ad6f5cfc 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -81,7 +81,10 @@ #define AddCrc(data,datalen) Iso15693AddCrc(data,datalen) #define sprintUID(target,uid) Iso15693sprintUID(target,uid) -int DEBUG=0; +// approximate amplitude=sqrt(ci^2+cq^2) +#define AMPLITUDE(ci, cq) (MAX(ABS(ci), ABS(cq)) + (MIN(ABS(ci), ABS(cq))>>1)) + +static int DEBUG = 0; // --------------------------- @@ -303,13 +306,9 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * // NOW READ RESPONSE FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); - //spindelay(60); // greg - experiment to get rid of some of the 0 byte/failed reads c = 0; getNext = false; for(;;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x43; - } if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { int8_t b; b = (int8_t)AT91C_BASE_SSC->SSC_RHR; @@ -319,11 +318,11 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * // every other is Q. We just want power, so abs(I) + abs(Q) is // close to what we want. if(getNext) { - uint8_t r = ABS(b) + ABS(prev); + uint8_t r = AMPLITUDE(b, prev); - dest[c++] = (uint8_t)r; + dest[c++] = r; - if(c >= 2000) { + if(c >= 4000) { break; } } else { @@ -341,12 +340,10 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * int i, j; int max = 0, maxPos=0; - int skip = 4; - - // if(GraphTraceLen < 1000) return; // THIS CHECKS FOR A BUFFER TO SMALL + int skip = 2; // First, correlate for SOF - for(i = 0; i < 100; i++) { + for(i = 0; i < 200; i++) { // usually, SOF is found around i = 60 int corr = 0; for(j = 0; j < arraylen(FrameSOF); j += skip) { corr += FrameSOF[j]*dest[i+(j/skip)]; @@ -356,7 +353,7 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * maxPos = i; } } - // Dbprintf("SOF at %d, correlation %d", maxPos,max/(arraylen(FrameSOF)/skip)); + if (DEBUG) Dbprintf("SOF at %d, correlation %d", maxPos, max/(arraylen(FrameSOF)/skip)); int k = 0; // this will be our return value @@ -370,10 +367,15 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * memset(outBuf, 0, sizeof(outBuf)); uint8_t mask = 0x01; for(;;) { - int corr0 = 0, corr1 = 0, corrEOF = 0; + int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0; for(j = 0; j < arraylen(Logic0); j += skip) { corr0 += Logic0[j]*dest[i+(j/skip)]; } + corr01 = corr00 = corr0; + for(j = 0; j < arraylen(Logic0); j += skip) { + corr00 += Logic0[j]*dest[i+arraylen(Logic0)/skip+(j/skip)]; + corr01 += Logic1[j]*dest[i+arraylen(Logic0)/skip+(j/skip)]; + } for(j = 0; j < arraylen(Logic1); j += skip) { corr1 += Logic1[j]*dest[i+(j/skip)]; } @@ -381,11 +383,14 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * corrEOF += FrameEOF[j]*dest[i+(j/skip)]; } // Even things out by the length of the target waveform. + corr00 *= 2; + corr01 *= 2; corr0 *= 4; corr1 *= 4; - if(corrEOF > corr1 && corrEOF > corr0) { - // Dbprintf("EOF at %d", i); + if(corrEOF > corr1 && corrEOF > corr00 && corrEOF > corr01) { + if (DEBUG) Dbprintf("EOF at %d, correlation %d (corr01: %d, corr00: %d, corr1: %d, corr0: %d)", + i, corrEOF, corr01, corr00, corr1, corr0); break; } else if(corr1 > corr0) { i += arraylen(Logic1)/skip; @@ -398,7 +403,7 @@ static int GetIso15693AnswerFromTag(uint8_t *receivedResponse, int maxLen, int * k++; mask = 0x01; } - if((i+(int)arraylen(FrameEOF)) >= 2000) { + if((i+(int)arraylen(FrameEOF)/skip) >= 4000) { DbpString("ran off end!"); break; } @@ -446,9 +451,6 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int c = 0; getNext = false; for(;;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x43; - } if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { int8_t b = (int8_t)AT91C_BASE_SSC->SSC_RHR; @@ -457,11 +459,11 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int // every other is Q. We just want power, so abs(I) + abs(Q) is // close to what we want. if(getNext) { - uint8_t r = ABS(b) + ABS(prev); + uint8_t r = AMPLITUDE(b, prev); - dest[c++] = (uint8_t)r; + dest[c++] = r; - if(c >= 20000) { + if(c >= BIGBUF_SIZE) { break; } } else { @@ -479,12 +481,10 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int int i, j; int max = 0, maxPos=0; - int skip = 4; - -// if(GraphTraceLen < 1000) return; // THIS CHECKS FOR A BUFFER TO SMALL + int skip = 2; // First, correlate for SOF - for(i = 0; i < 19000; i++) { + for(i = 0; i < 38000; i++) { int corr = 0; for(j = 0; j < arraylen(FrameSOF); j += skip) { corr += FrameSOF[j]*dest[i+(j/skip)]; @@ -494,7 +494,7 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int maxPos = i; } } -// DbpString("SOF at %d, correlation %d", maxPos,max/(arraylen(FrameSOF)/skip)); + if (DEBUG) Dbprintf("SOF at %d, correlation %d", maxPos,max/(arraylen(FrameSOF)/skip)); int k = 0; // this will be our return value @@ -508,10 +508,15 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int memset(outBuf, 0, sizeof(outBuf)); uint8_t mask = 0x01; for(;;) { - int corr0 = 0, corr1 = 0, corrEOF = 0; + int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0; for(j = 0; j < arraylen(Logic0); j += skip) { corr0 += Logic0[j]*dest[i+(j/skip)]; } + corr01 = corr00 = corr0; + for(j = 0; j < arraylen(Logic0); j += skip) { + corr00 += Logic0[j]*dest[i+arraylen(Logic0)/skip+(j/skip)]; + corr01 += Logic1[j]*dest[i+arraylen(Logic0)/skip+(j/skip)]; + } for(j = 0; j < arraylen(Logic1); j += skip) { corr1 += Logic1[j]*dest[i+(j/skip)]; } @@ -519,11 +524,14 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int corrEOF += FrameEOF[j]*dest[i+(j/skip)]; } // Even things out by the length of the target waveform. + corr00 *= 2; + corr01 *= 2; corr0 *= 4; corr1 *= 4; - if(corrEOF > corr1 && corrEOF > corr0) { - // DbpString("EOF at %d", i); + if(corrEOF > corr1 && corrEOF > corr00 && corrEOF > corr01) { + if (DEBUG) Dbprintf("EOF at %d, correlation %d (corr01: %d, corr00: %d, corr1: %d, corr0: %d)", + i, corrEOF, corr01, corr00, corr1, corr0); break; } else if(corr1 > corr0) { i += arraylen(Logic1)/skip; @@ -536,7 +544,7 @@ static int GetIso15693AnswerFromSniff(uint8_t *receivedResponse, int maxLen, int k++; mask = 0x01; } - if((i+(int)arraylen(FrameEOF)) >= 2000) { + if((i+(int)arraylen(FrameEOF)/skip) >= BIGBUF_SIZE) { DbpString("ran off end!"); break; } @@ -602,10 +610,6 @@ void AcquireRawAdcSamplesIso15693(void) break; } } - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - volatile uint32_t r = AT91C_BASE_SSC->SSC_RHR; - (void)r; - } WDT_HIT(); } @@ -614,9 +618,6 @@ void AcquireRawAdcSamplesIso15693(void) c = 0; getNext = false; for(;;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x43; - } if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { int8_t b; b = (int8_t)AT91C_BASE_SSC->SSC_RHR; @@ -626,11 +627,11 @@ void AcquireRawAdcSamplesIso15693(void) // every other is Q. We just want power, so abs(I) + abs(Q) is // close to what we want. if(getNext) { - uint8_t r = ABS(b) + ABS(prev); + uint8_t r = AMPLITUDE(b, prev); - dest[c++] = (uint8_t)r; + dest[c++] = r; - if(c >= 2000) { + if(c >= 4000) { break; } } else { @@ -668,9 +669,6 @@ void RecordRawAdcSamplesIso15693(void) c = 0; getNext = false; for(;;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x43; - } if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { int8_t b; b = (int8_t)AT91C_BASE_SSC->SSC_RHR; @@ -680,11 +678,11 @@ void RecordRawAdcSamplesIso15693(void) // every other is Q. We just want power, so abs(I) + abs(Q) is // close to what we want. if(getNext) { - uint8_t r = ABS(b) + ABS(prev); + uint8_t r = AMPLITUDE(b, prev); - dest[c++] = (uint8_t)r; + dest[c++] = r; - if(c >= 7000) { + if(c >= 14000) { break; } } else { @@ -836,7 +834,7 @@ int SendDataTag(uint8_t *send, int sendlen, int init, int speed, uint8_t **recv) if (init) Iso15693InitReader(); int answerLen=0; - uint8_t *answer = BigBuf_get_addr() + 3660; + uint8_t *answer = BigBuf_get_addr() + 4000; if (recv != NULL) memset(answer, 0, 100); if (!speed) { @@ -957,7 +955,7 @@ void ReaderIso15693(uint32_t parameter) int answerLen1 = 0; int answerLen2 = 0; - int answerLen3 = 0; + // int answerLen3 = 0; int i = 0; int samples = 0; int tsamples = 0; @@ -967,11 +965,11 @@ void ReaderIso15693(uint32_t parameter) FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - uint8_t *answer1 = BigBuf_get_addr() + 3660; - uint8_t *answer2 = BigBuf_get_addr() + 3760; - uint8_t *answer3 = BigBuf_get_addr() + 3860; + uint8_t *answer1 = BigBuf_get_addr() + 4000; + uint8_t *answer2 = BigBuf_get_addr() + 4100; + // uint8_t *answer3 = BigBuf_get_addr() + 4200; // Blank arrays - memset(answer1, 0x00, 300); + memset(answer1, 0x00, 200); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); // Setup SSC @@ -1025,21 +1023,21 @@ void ReaderIso15693(uint32_t parameter) TagUID[3],TagUID[2],TagUID[1],TagUID[0]); - Dbprintf("%d octets read from SELECT request:", answerLen2); - DbdecodeIso15693Answer(answerLen2,answer2); - Dbhexdump(answerLen2,answer2,true); + // Dbprintf("%d octets read from SELECT request:", answerLen2); + // DbdecodeIso15693Answer(answerLen2,answer2); + // Dbhexdump(answerLen2,answer2,true); - Dbprintf("%d octets read from XXX request:", answerLen3); - DbdecodeIso15693Answer(answerLen3,answer3); - Dbhexdump(answerLen3,answer3,true); + // Dbprintf("%d octets read from XXX request:", answerLen3); + // DbdecodeIso15693Answer(answerLen3,answer3); + // Dbhexdump(answerLen3,answer3,true); // read all pages if (answerLen1>=12 && DEBUG) { i=0; while (i<32) { // sanity check, assume max 32 pages BuildReadBlockRequest(TagUID,i); - TransmitTo15693Tag(ToSend,ToSendMax,&tsamples, &wait); - answerLen2 = GetIso15693AnswerFromTag(answer2, 100, &samples, &elapsed); + TransmitTo15693Tag(ToSend,ToSendMax,&tsamples, &wait); + answerLen2 = GetIso15693AnswerFromTag(answer2, 100, &samples, &elapsed); if (answerLen2>0) { Dbprintf("READ SINGLE BLOCK %d returned %d octets:",i,answerLen2); DbdecodeIso15693Answer(answerLen2,answer2); @@ -1073,7 +1071,7 @@ void SimTagIso15693(uint32_t parameter, uint8_t *uid) FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - uint8_t *buf = BigBuf_get_addr() + 3660; + uint8_t *buf = BigBuf_get_addr() + 4000; memset(buf, 0x00, 100); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); @@ -1177,7 +1175,7 @@ void DirectTag15693Command(uint32_t datalen,uint32_t speed, uint32_t recv, uint8 if (recv) { LED_B_ON(); - cmd_send(CMD_ACK,recvlen>48?48:recvlen,0,0,recvbuf,48); + cmd_send(CMD_ACK,recvlen>48?48:recvlen,0,0,recvbuf,48); LED_B_OFF(); if (DEBUG) { diff --git a/client/cmdhf15.c b/client/cmdhf15.c index 08cc3b15..b6a84c1c 100644 --- a/client/cmdhf15.c +++ b/client/cmdhf15.c @@ -268,7 +268,7 @@ static char* TagErrorStr(uint8_t error) { case 0x02: return "The command is not recognised"; case 0x03: return "The option is not supported."; case 0x0f: return "Unknown error."; - case 0x10: return "The specified block is not available (doesn鈥檛 exist)."; + case 0x10: return "The specified block is not available (doesn't exist)."; case 0x11: return "The specified block is already -locked and thus cannot be locked again"; case 0x12: return "The specified block is locked and its content cannot be changed."; case 0x13: return "The specified block was not successfully programmed."; @@ -286,12 +286,12 @@ int CmdHF15Demod(const char *Cmd) int i, j; int max = 0, maxPos = 0; - int skip = 4; + int skip = 2; - if (GraphTraceLen < 1000) return 0; + if (GraphTraceLen < 2000) return 0; // First, correlate for SOF - for (i = 0; i < 100; i++) { + for (i = 0; i < 200; i++) { int corr = 0; for (j = 0; j < arraylen(FrameSOF); j += skip) { corr += FrameSOF[j] * GraphBuffer[i + (j / skip)]; @@ -310,23 +310,30 @@ int CmdHF15Demod(const char *Cmd) memset(outBuf, 0, sizeof(outBuf)); uint8_t mask = 0x01; for (;;) { - int corr0 = 0, corr1 = 0, corrEOF = 0; - for (j = 0; j < arraylen(Logic0); j += skip) { - corr0 += Logic0[j] * GraphBuffer[i + (j / skip)]; - } - for (j = 0; j < arraylen(Logic1); j += skip) { - corr1 += Logic1[j] * GraphBuffer[i + (j / skip)]; - } - for (j = 0; j < arraylen(FrameEOF); j += skip) { - corrEOF += FrameEOF[j] * GraphBuffer[i + (j / skip)]; - } - // Even things out by the length of the target waveform. - corr0 *= 4; - corr1 *= 4; - - if (corrEOF > corr1 && corrEOF > corr0) { - PrintAndLog("EOF at %d", i); - break; + int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0; + for(j = 0; j < arraylen(Logic0); j += skip) { + corr0 += Logic0[j]*GraphBuffer[i+(j/skip)]; + } + corr01 = corr00 = corr0; + for(j = 0; j < arraylen(Logic0); j += skip) { + corr00 += Logic0[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; + corr01 += Logic1[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; + } + for(j = 0; j < arraylen(Logic1); j += skip) { + corr1 += Logic1[j]*GraphBuffer[i+(j/skip)]; + } + for(j = 0; j < arraylen(FrameEOF); j += skip) { + corrEOF += FrameEOF[j]*GraphBuffer[i+(j/skip)]; + } + // Even things out by the length of the target waveform. + corr00 *= 2; + corr01 *= 2; + corr0 *= 4; + corr1 *= 4; + + if(corrEOF > corr1 && corrEOF > corr00 && corrEOF > corr01) { + PrintAndLog("EOF at %d", i); + break; } else if (corr1 > corr0) { i += arraylen(Logic1) / skip; outBuf[k] |= mask; diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 229151dfbf50cb8de730a85446ccbc18407e8474..939ba93a54ce00e9425a1156d3900f1af58c4c8e 100644 GIT binary patch literal 42175 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 literal 42175 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 Z%S`zFE0UDq-{Xn`{Cl{VmhxZne*^UawK@O* diff --git a/fpga/hi_read_rx_xcorr.v b/fpga/hi_read_rx_xcorr.v index 433d6736..f637abf2 100644 --- a/fpga/hi_read_rx_xcorr.v +++ b/fpga/hi_read_rx_xcorr.v @@ -27,22 +27,12 @@ assign pwr_hi = ck_1356megb & (~snoop); assign pwr_oe1 = 1'b0; assign pwr_oe3 = 1'b0; assign pwr_oe4 = 1'b0; +// Unused. +assign pwr_lo = 1'b0; +assign pwr_oe2 = 1'b0; -reg [2:0] fc_div; -always @(negedge ck_1356megb) - fc_div <= fc_div + 1; +assign adc_clk = ck_1356megb; // sample frequency is 13,56 MHz -(* clock_signal = "yes" *) reg adc_clk; // sample frequency, always 16 * fc -always @(ck_1356megb, xcorr_is_848, xcorr_quarter_freq, fc_div) - if (xcorr_is_848 & ~xcorr_quarter_freq) // fc = 847.5 kHz, standard ISO14443B - adc_clk <= ck_1356megb; - else if (~xcorr_is_848 & ~xcorr_quarter_freq) // fc = 423.75 kHz - adc_clk <= fc_div[0]; - else if (xcorr_is_848 & xcorr_quarter_freq) // fc = 211.875 kHz - adc_clk <= fc_div[1]; - else // fc = 105.9375 kHz - adc_clk <= fc_div[2]; - // When we're a reader, we just need to do the BPSK demod; but when we're an // eavesdropper, we also need to pick out the commands sent by the reader, // using AM. Do this the same way that we do it for the simulated tag. @@ -69,15 +59,27 @@ begin end end -// Let us report a correlation every 4 subcarrier cycles, or 4*16=64 samples, -// so we need a 6-bit counter. + +// Let us report a correlation every 64 samples. I.e. +// one Q/I pair after 4 subcarrier cycles for the 848kHz subcarrier, +// one Q/I pair after 2 subcarrier cycles for the 424kHz subcarriers, +// one Q/I pair for each subcarrier cyle for the 212kHz subcarrier. +// We need a 6-bit counter for the timing. reg [5:0] corr_i_cnt; -// And a couple of registers in which to accumulate the correlations. -// We would add at most 32 times the difference between unmodulated and modulated signal. It should +always @(negedge adc_clk) +begin + corr_i_cnt <= corr_i_cnt + 1; +end + +// And a couple of registers in which to accumulate the correlations. From the 64 samples +// we would add at most 32 times the difference between unmodulated and modulated signal. It should // be safe to assume that a tag will not be able to modulate the carrier signal by more than 25%. // 32 * 255 * 0,25 = 2040, which can be held in 11 bits. Add 1 bit for sign. -reg signed [11:0] corr_i_accum; -reg signed [11:0] corr_q_accum; +// Temporary we might need more bits. For the 212kHz subcarrier we could possible add 32 times the +// maximum signal value before a first subtraction would occur. 32 * 255 = 8160 can be held in 13 bits. +// Add one bit for sign -> need 14 bit registers but final result will fit into 12 bits. +reg signed [13:0] corr_i_accum; +reg signed [13:0] corr_q_accum; // we will report maximum 8 significant bits reg signed [7:0] corr_i_out; reg signed [7:0] corr_q_out; @@ -86,12 +88,29 @@ reg ssp_clk; reg ssp_frame; -always @(negedge adc_clk) -begin - corr_i_cnt <= corr_i_cnt + 1; -end - +// The subcarrier reference signals +reg subcarrier_I; +reg subcarrier_Q; +always @(corr_i_cnt or xcorr_is_848 or xcorr_quarter_freq) +begin + if (xcorr_is_848 & ~xcorr_quarter_freq) // 848 kHz + begin + subcarrier_I = ~corr_i_cnt[3]; + subcarrier_Q = ~(corr_i_cnt[3] ^ corr_i_cnt[2]); + end + else if (xcorr_is_848 & xcorr_quarter_freq) // 212 kHz + begin + subcarrier_I = ~corr_i_cnt[5]; + subcarrier_Q = ~(corr_i_cnt[5] ^ corr_i_cnt[4]); + end + else + begin // 424 kHz + subcarrier_I = ~corr_i_cnt[4]; + subcarrier_Q = ~(corr_i_cnt[4] ^ corr_i_cnt[3]); + end +end + // ADC data appears on the rising edge, so sample it on the falling edge always @(negedge adc_clk) begin @@ -103,36 +122,60 @@ begin if(snoop) begin // Send 7 most significant bits of tag signal (signed), plus 1 bit reader signal - corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; - corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= {7'b0111111, after_hysteresis_prev_prev}; + else + corr_i_out <= {7'b1000000, after_hysteresis_prev_prev}; + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= {7'b0111111, after_hysteresis_prev}; + else + corr_q_out <= {7'b1000000, after_hysteresis_prev}; after_hysteresis_prev_prev <= after_hysteresis; end else begin - // 8 bits of tag signal - corr_i_out <= corr_i_accum[11:4]; - corr_q_out <= corr_q_accum[11:4]; + // Send 8 bits of tag signal + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= corr_i_accum[11:4]; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= 8'b01111111; + else + corr_i_out <= 8'b10000000; + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= corr_q_accum[11:4]; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= 8'b01111111; + else + corr_q_out <= 8'b10000000; end - - corr_i_accum <= adc_d; - corr_q_accum <= adc_d; + // Initialize next correlation. + // Both I and Q reference signals are high when corr_i_nct == 0. Therefore need to accumulate. + corr_i_accum <= $signed({1'b0,adc_d}); + corr_q_accum <= $signed({1'b0,adc_d}); end else begin - if(corr_i_cnt[3]) - corr_i_accum <= corr_i_accum - adc_d; + if (subcarrier_I) + corr_i_accum <= corr_i_accum + $signed({1'b0,adc_d}); else - corr_i_accum <= corr_i_accum + adc_d; + corr_i_accum <= corr_i_accum - $signed({1'b0,adc_d}); - if(corr_i_cnt[3] == corr_i_cnt[2]) // phase shifted by pi/2 - corr_q_accum <= corr_q_accum + adc_d; + if (subcarrier_Q) + corr_q_accum <= corr_q_accum + $signed({1'b0,adc_d}); else - corr_q_accum <= corr_q_accum - adc_d; + corr_q_accum <= corr_q_accum - $signed({1'b0,adc_d}); end - // The logic in hi_simulate.v reports 4 samples per bit. We report two - // (I, Q) pairs per bit, so we should do 2 samples per pair. + // for each Q/I pair report two reader signal samples when sniffing if(corr_i_cnt == 6'd32) after_hysteresis_prev <= after_hysteresis; @@ -167,8 +210,4 @@ assign ssp_din = corr_i_out[7]; assign dbg = corr_i_cnt[3]; -// Unused. -assign pwr_lo = 1'b0; -assign pwr_oe2 = 1'b0; - endmodule From da05bc6eca632aa8ef5251942919ab8650191357 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Mon, 20 Aug 2018 22:29:34 +0200 Subject: [PATCH 2/4] Legic: rewrite reader to use xcorrelation and precise timing (#654) * Legic: rewrite reader to use xcorrelation and precise timing - Even tough Legic tags transmit just AM, receiving using xcorrelation results in a significantly better signal quality. - Switching from bit bang to a hardware based ssc frees up CPU time for other tasks e.g. prng and demodulation - Having all times based on a fixed ts, results in perfect rwd-tag synchronization without magic +/- calculations. * hi_read_tx: remove jerry-riged hysteresis based receiver - This feature got obsolete by a x-correlation based receiver. * Legic: adjusted sampling to new ssp clock speed - Sampling is 4 times faster and pipeline daly reduced to 1/4. The new code samples each bit earyler to account for the shorter pipeline. That introduced bit errors by leeking the next bit into the current one. * Legic: average 8 samples for better noise rejection. * Update CHANGELOG.md --- CHANGELOG.md | 1 + armsrc/legicrf.c | 843 +++++++++++++++++++++++++--------------------- armsrc/legicrf.h | 2 +- fpga/hi_read_tx.v | 19 +- include/legic.h | 27 ++ 5 files changed, 492 insertions(+), 400 deletions(-) create mode 100644 include/legic.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 13fc97fb..a7e2e045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed `hf 14a reader` to just reqest-anticilission-select sequence (Merlok) - Changed `hf 14a raw` - works with LED's and some exchange logic (Merlok) - Changed TLV parser messages to more convenient (Merlok) +- Rewritten Legic Prime reader (`hf legic reader`, `write` and `fill`) - it is using xcorrelation now (AntiCat) ### Fixed - Changed start sequence in Qt mode (fix: short commands hangs main Qt thread) (Merlok) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 27dcc297..a611c741 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -1,5 +1,7 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Pl枚tz +// 2016 Iceman +// 2018 AntiCat (rwd rewritten) // // 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 @@ -15,6 +17,7 @@ #include "legicrf.h" #include "legic_prng.h" +#include "legic.h" #include "crc.h" static struct legic_frame { @@ -40,6 +43,460 @@ static int legic_reqresp_drift; AT91PS_TC timer; AT91PS_TC prng_timer; +static legic_card_select_t card;/* metadata of currently selected card */ + +//----------------------------------------------------------------------------- +// Frame timing and pseudorandom number generator +// +// The Prng is forwarded every 100us (TAG_BIT_PERIOD), except when the reader is +// transmitting. In that case the prng has to be forwarded every bit transmitted: +// - 60us for a 0 (RWD_TIME_0) +// - 100us 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 GET_TICKS, during computational and wait periodes. +// +// 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 RWD_TIME_PAUSE 30 /* 20us */ +#define RWD_TIME_1 150 /* READER_TIME_PAUSE 20us off + 80us on = 100us */ +#define RWD_TIME_0 90 /* READER_TIME_PAUSE 20us off + 40us on = 60us */ +#define RWD_FRAME_WAIT 330 /* 220us from TAG frame end to READER frame start */ +#define TAG_FRAME_WAIT 495 /* 330us from READER frame end to TAG frame start */ +#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 */ + +#define SESSION_IV 0x55 /* An arbitrary chose session IV, all shoud work */ +#define OFFSET_LOG 1024 /* The largest Legic Prime card is 1k */ +#define WRITE_LOWERLIMIT 4 /* UID and MCC are not writable */ + +#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) +//----------------------------------------------------------------------------- + +static inline uint8_t rx_byte_from_fpga() { + for(;;) { + WDT_HIT(); + + // wait for byte be become available in rx holding register + if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { + return AT91C_BASE_SSC->SSC_RHR; + } + } +} + +//----------------------------------------------------------------------------- +// Demodulation (Reader) +//----------------------------------------------------------------------------- + +// Returns a demedulated bit +// +// The FPGA running xcorrelation samples the subcarrier at ~13.56 MHz. The mode +// was initialy designed to receive BSPK/2-PSK. Hance, it reports an I/Q pair +// every 4.7us (8 bits i and 8 bits q). +// +// The subcarrier amplitude can be calculated using Pythagoras sqrt(i^2 + q^2). +// To reduce CPU time the amplitude is approximated by using linear functions: +// am = MAX(ABS(i),ABS(q)) + 1/2*MIN(ABS(i),ABSq)) +// +// Note: The SSC receiver is never synchronized the calculation my be performed +// on a I/Q pair from two subsequent correlations, but does not matter. +// +// The bit time is 99.1us (21 I/Q pairs). The receiver skips the first 5 samples +// and averages the next (most stable) 8 samples. The final 8 samples are dropped +// 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() { + int32_t cq = 0; + int32_t ci = 0; + + // skip first 5 I/Q pairs + for(size_t i = 0; i<5; ++i) { + (int8_t)rx_byte_from_fpga(); + (int8_t)rx_byte_from_fpga(); + } + + // sample next 8 I/Q pairs + for(size_t i = 0; i<8; ++i) { + cq += (int8_t)rx_byte_from_fpga(); + ci += (int8_t)rx_byte_from_fpga(); + } + + // calculate power + int32_t power = (MAX(ABS(ci), ABS(cq)) + (MIN(ABS(ci), ABS(cq)) >> 1)); + + // compare average (power / 8) to threshold + return ((power >> 3) > INPUT_THRESHOLD); +} + +//----------------------------------------------------------------------------- +// Modulation (Reader) +// +// I've tried to modulate the Legic specific pause-puls using ssc and the default +// ssc clock of 105.4 kHz (bit periode of 9.4us) - previous commit. However, +// the timing was not precise enough. By increasing the ssc clock this could +// be circumvented, but the adventage over bitbang would be little. +//----------------------------------------------------------------------------- + +static inline void tx_bit_as_reader(bool bit) { + // insert pause + LOW(GPIO_SSC_DOUT); + last_frame_end += RWD_TIME_PAUSE; + while(GET_TICKS < last_frame_end) { }; + HIGH(GPIO_SSC_DOUT); + + // return to high, wait for bit periode to end + last_frame_end += (bit ? RWD_TIME_1 : RWD_TIME_0) - RWD_TIME_PAUSE; + while(GET_TICKS < last_frame_end) { }; +} + +//----------------------------------------------------------------------------- +// Frame Handling (Reader) +// +// The LEGIC RF protocol from card to reader does not include explicit frame +// start/stop information or length information. The reader must know beforehand +// how many bits it wants to receive. +// Notably: a card sending a stream of 0-bits is indistinguishable from no card +// present. +//----------------------------------------------------------------------------- + +static void tx_frame_as_reader(uint32_t frame, uint8_t len) { + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); + + // wait for next tx timeslot + last_frame_end += RWD_FRAME_WAIT; + while(GET_TICKS < last_frame_end) { }; + + // 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()); + legic_prng_forward(1); + }; + + // add pause to mark end of the frame + LOW(GPIO_SSC_DOUT); + last_frame_end += RWD_TIME_PAUSE; + while(GET_TICKS < last_frame_end) { }; + HIGH(GPIO_SSC_DOUT); +} + +static uint32_t rx_frame_as_reader(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); + + // hold sampling until card is expected to respond + last_frame_end += TAG_FRAME_WAIT; + 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; + legic_prng_forward(1); + + // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + while(GET_TICKS < last_frame_end) { }; + } + + return frame; +} + +static bool rx_ack_as_reader() { + // change fpga into rx mode + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR + | FPGA_HF_READER_RX_XCORR_848_KHZ + | FPGA_HF_READER_RX_XCORR_QUARTER_FREQ); + + // hold sampling until card is expected to respond + last_frame_end += TAG_FRAME_WAIT; + while(GET_TICKS < last_frame_end) { }; + + uint32_t ack = 0; + for(uint8_t i = 0; i < TAG_WRITE_TIMEOUT; ++i) { + // sample bit + ack = rx_bit_as_reader(); + legic_prng_forward(1); + + // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + while(GET_TICKS < last_frame_end) { }; + + // check if it was an ACK + if(ack) { + break; + } + } + + return ack; +} + +//----------------------------------------------------------------------------- +// Legic Reader +//----------------------------------------------------------------------------- + +int init_card(uint8_t cardtype, legic_card_select_t *p_card) { + p_card->tagtype = cardtype; + + switch(p_card->tagtype) { + case 0x0d: + p_card->cmdsize = 6; + p_card->addrsize = 5; + p_card->cardsize = 22; + break; + case 0x1d: + p_card->cmdsize = 9; + p_card->addrsize = 8; + p_card->cardsize = 256; + break; + case 0x3d: + 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_reader(bool clear_mem) { + // configure FPGA + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR + | FPGA_HF_READER_RX_XCORR_848_KHZ + | FPGA_HF_READER_RX_XCORR_QUARTER_FREQ); + SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + LED_D_ON(); + + // configure SSC with defaults + FpgaSetupSsc(); + + // re-claim GPIO_SSC_DOUT as GPIO and enable output + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + HIGH(GPIO_SSC_DOUT); + + // init crc calculator + crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); + + // start us timer + StartTicks(); +} + +// Setup reader to card connection +// +// 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) { + // init coordination timestamp + last_frame_end = GET_TICKS; + + // Switch on carrier and let the card charge for 5ms. + last_frame_end += 7500; + while(GET_TICKS < last_frame_end) { }; + + legic_prng_init(0); + tx_frame_as_reader(iv, 7); + + // configure iv + legic_prng_init(iv); + legic_prng_forward(2); + + // receive card type + int32_t card_type = rx_frame_as_reader(6); + legic_prng_forward(3); + + // send obsfuscated acknowledgment frame + switch (card_type) { + case 0x0D: + tx_frame_as_reader(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 + break; + case 0x1D: + case 0x3D: + tx_frame_as_reader(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 + break; + } + + return card_type; +} + +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 int16_t read_byte(uint16_t index, uint8_t cmd_sz) { + uint16_t cmd = (index << 1) | LEGIC_READ; + + // read one byte + LED_B_ON(); + legic_prng_forward(2); + tx_frame_as_reader(cmd, cmd_sz); + legic_prng_forward(2); + uint32_t frame = rx_frame_as_reader(12); + LED_B_OFF(); + + // split frame into data and crc + uint8_t byte = BYTEx(frame, 0); + uint8_t crc = BYTEx(frame, 1); + + // check received against calculated crc + uint8_t calc_crc = calc_crc4(cmd, cmd_sz, byte); + if(calc_crc != crc) { + Dbprintf("!!! crc mismatch: %x != %x !!!", calc_crc, crc); + return -1; + } + + legic_prng_forward(1); + + return byte; +} + +// Transmit write command, wait until (3.6ms) the tag sends back an unencrypted +// ACK ('1' bit) and forward the prng time based. +bool write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { + uint32_t cmd = index << 1 | LEGIC_WRITE; // prepare command + uint8_t crc = calc_crc4(cmd, addr_sz + 1, byte); // calculate crc + cmd |= byte << (addr_sz + 1); // append value + cmd |= (crc & 0xF) << (addr_sz + 1 + 8); // and crc + + // 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 + legic_prng_forward(3); + LED_C_OFF(); + + // wait for ack + return rx_ack_as_reader(); +} + +//----------------------------------------------------------------------------- +// Command Line Interface +// +// Only this functions are public / called from appmain.c +//----------------------------------------------------------------------------- +void LegicRfReader(int offset, int bytes) { + uint8_t *BigBuf = BigBuf_get_addr(); + memset(BigBuf, 0, 1024); + + // configure ARM and FPGA + init_reader(false); + + // establish shared secret and detect card type + DbpString("Reading card ..."); + uint8_t card_type = setup_phase_reader(SESSION_IV); + if(init_card(card_type, &card) != 0) { + Dbprintf("No or unknown card found, aborting"); + goto OUT; + } + + // if no argument is specified create full dump + if(bytes == -1) { + bytes = card.cardsize; + } + + // do not read beyond card memory + if(bytes + offset > card.cardsize) { + bytes = card.cardsize - offset; + } + + for(uint16_t i = 0; i < bytes; ++i) { + int16_t byte = read_byte(offset + i, card.cmdsize); + if(byte == -1) { + Dbprintf("operation failed @ 0x%03.3x", bytes); + goto OUT; + } + BigBuf[i] = byte; + } + + // OK + Dbprintf("Card (MIM %i) read, use 'hf legic decode' or", card.cardsize); + Dbprintf("'data hexsamples %d' to view results", (bytes+7) & ~7); + +OUT: + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_B_OFF(); + LED_C_OFF(); + LED_D_OFF(); + StopTicks(); +} + +void LegicRfWriter(int bytes, int offset) { + uint8_t *BigBuf = BigBuf_get_addr(); + + // configure ARM and FPGA + init_reader(false); + + // uid is not writeable + if(offset <= WRITE_LOWERLIMIT) { + goto OUT; + } + + // 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); + if(init_card(card_type, &card) != 0) { + Dbprintf("No or unknown card found, aborting"); + goto OUT; + } + + // do not write beyond card memory + if(bytes + offset > card.cardsize) { + bytes = card.cardsize - offset; + } + + // write in reverse order, only then is DCF (decremental field) writable + while(bytes-- > 0 && !BUTTON_PRESS()) { + if(!write_byte(bytes + offset, BigBuf[bytes], card.addrsize)) { + Dbprintf("operation failed @ 0x%03.3x", bytes); + goto OUT; + } + } + + // OK + DbpString("Write successful"); + +OUT: + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_B_OFF(); + LED_C_OFF(); + 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 @@ -62,22 +519,6 @@ static void setup_timer(void) prng_timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; } -/* At TIMER_CLOCK3 (MCK/32) */ -#define RWD_TIME_1 150 /* RWD_TIME_PAUSE off, 80us on = 100us */ -#define RWD_TIME_0 90 /* RWD_TIME_PAUSE off, 40us on = 60us */ -#define RWD_TIME_PAUSE 30 /* 20us */ -#define RWD_TIME_FUZZ 20 /* rather generous 13us, since the peak detector + hysteresis fuzz quite a bit */ -#define TAG_TIME_BIT 150 /* 100us for every bit */ -#define TAG_TIME_WAIT 490 /* time from RWD frame end to tag frame start, experimentally determined */ - -#define SIM_DIVISOR 586 /* prng_time/SIM_DIVISOR count prng needs to be forwared */ -#define SIM_SHIFT 900 /* prng_time+SIM_SHIFT shift of delayed start */ - -#define SESSION_IV 0x55 -#define OFFSET_LOG 1024 - -#define FUZZ_EQUAL(value, target, fuzz) ((value) > ((target)-(fuzz)) && (value) < ((target)+(fuzz))) - /* Generate Keystream */ static uint32_t get_key_stream(int skip, int count) { @@ -138,11 +579,11 @@ static void frame_send_tag(uint16_t response, int bits, int crypt) } /* Wait for the frame start */ - while(timer->TC_CV < (TAG_TIME_WAIT - 30)) ; + while(timer->TC_CV < (TAG_FRAME_WAIT - 30)) ; int i; for(i=0; iTC_CV + TAG_TIME_BIT; + int nextbit = timer->TC_CV + TAG_BIT_PERIOD; int bit = response & 1; response = response >> 1; if(bit) { @@ -155,126 +596,6 @@ static void frame_send_tag(uint16_t response, int bits, int crypt) AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; } -/* Send a frame in reader mode, the FPGA must have been set up by - * LegicRfReader - */ -static void frame_send_rwd(uint32_t data, int bits) -{ - /* Start clock */ - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - while(timer->TC_CV > 1) ; /* Wait till the clock has reset */ - - int i; - for(i=0; iTC_CV; - int pause_end = starttime + RWD_TIME_PAUSE, bit_end; - int bit = data & 1; - data = data >> 1; - - if(bit ^ legic_prng_get_bit()) { - bit_end = starttime + RWD_TIME_1; - } else { - bit_end = starttime + RWD_TIME_0; - } - - /* RWD_TIME_PAUSE time off, then some time on, so that the complete bit time is - * RWD_TIME_x, where x is the bit to be transmitted */ - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - while(timer->TC_CV < pause_end) ; - AT91C_BASE_PIOA->PIO_SODR = GPIO_SSC_DOUT; - legic_prng_forward(1); /* bit duration is longest. use this time to forward the lfsr */ - - while(timer->TC_CV < bit_end) ; - } - - { - /* One final pause to mark the end of the frame */ - int pause_end = timer->TC_CV + RWD_TIME_PAUSE; - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - while(timer->TC_CV < pause_end) ; - AT91C_BASE_PIOA->PIO_SODR = GPIO_SSC_DOUT; - } - - /* Reset the timer, to measure time until the start of the tag frame */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1) ; /* Wait till the clock has reset */ -} - -/* Receive a frame from the card in reader emulation mode, the FPGA and - * timer must have been set up by LegicRfReader and frame_send_rwd. - * - * The LEGIC RF protocol from card to reader does not include explicit - * frame start/stop information or length information. The reader must - * know beforehand how many bits it wants to receive. (Notably: a card - * sending a stream of 0-bits is indistinguishable from no card present.) - * - * Receive methodology: There is a fancy correlator in hi_read_rx_xcorr, but - * I'm not smart enough to use it. Instead I have patched hi_read_tx to output - * the ADC signal with hysteresis on SSP_DIN. Bit-bang that signal and look - * for edges. Count the edges in each bit interval. If they are approximately - * 0 this was a 0-bit, if they are approximately equal to the number of edges - * expected for a 212kHz subcarrier, this was a 1-bit. For timing we use the - * timer that's still running from frame_send_rwd in order to get a synchronization - * with the frame that we just sent. - * - * FIXME: Because we're relying on the hysteresis to just do the right thing - * the range is severely reduced (and you'll probably also need a good antenna). - * So this should be fixed some time in the future for a proper receiver. - */ -static void frame_receive_rwd(struct legic_frame * const f, int bits, int crypt) -{ - uint32_t the_bit = 1; /* Use a bitmask to save on shifts */ - uint32_t data=0; - int i, old_level=0, edges=0; - int next_bit_at = TAG_TIME_WAIT; - - if(bits > 32) { - bits = 32; - } - - AT91C_BASE_PIOA->PIO_ODR = GPIO_SSC_DIN; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DIN; - - /* we have some time now, precompute the cipher - * since we cannot compute it on the fly while reading */ - legic_prng_forward(2); - - if(crypt) - { - for(i=0; iTC_CV < next_bit_at) ; - - next_bit_at += TAG_TIME_BIT; - - for(i=0; iTC_CV < next_bit_at) { - int level = (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - if(level != old_level) - edges++; - old_level = level; - } - next_bit_at += TAG_TIME_BIT; - - if(edges > 20 && edges < 60) { /* expected are 42 edges */ - data ^= the_bit; - } - the_bit <<= 1; - } - - f->data = data; - f->bits = bits; - - /* Reset the timer, to synchronize the next frame */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1) ; /* Wait till the clock has reset */ -} - static void frame_append_bit(struct legic_frame * const f, int bit) { if(f->bits >= 31) { @@ -290,250 +611,6 @@ static void frame_clean(struct legic_frame * const f) f->bits = 0; } -static uint32_t perform_setup_phase_rwd(int iv) -{ - - /* Switch on carrier and let the tag charge for 1ms */ - AT91C_BASE_PIOA->PIO_SODR = GPIO_SSC_DOUT; - SpinDelay(1); - - legic_prng_init(0); /* no keystream yet */ - frame_send_rwd(iv, 7); - legic_prng_init(iv); - - frame_clean(¤t_frame); - frame_receive_rwd(¤t_frame, 6, 1); - legic_prng_forward(1); /* we wait anyways */ - while(timer->TC_CV < 387) ; /* ~ 258us */ - frame_send_rwd(0x19, 6); - - return current_frame.data; -} - -static void LegicCommonInit(void) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - FpgaSetupSsc(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); - - /* Bitbang the transmitter */ - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - - setup_timer(); - - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); -} - -static void switch_off_tag_rwd(void) -{ - /* Switch off carrier, make sure tag is reset */ - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - SpinDelay(10); - - WDT_HIT(); -} -/* calculate crc for a legic command */ -static int LegicCRC(int byte_index, int value, int cmd_sz) { - crc_clear(&legic_crc); - crc_update(&legic_crc, 1, 1); /* CMD_READ */ - crc_update(&legic_crc, byte_index, cmd_sz-1); - crc_update(&legic_crc, value, 8); - return crc_finish(&legic_crc); -} - -int legic_read_byte(int byte_index, int cmd_sz) { - int byte; - - legic_prng_forward(4); /* we wait anyways */ - while(timer->TC_CV < 387) ; /* ~ 258us + 100us*delay */ - - frame_send_rwd(1 | (byte_index << 1), cmd_sz); - frame_clean(¤t_frame); - - frame_receive_rwd(¤t_frame, 12, 1); - - byte = current_frame.data & 0xff; - if( LegicCRC(byte_index, byte, cmd_sz) != (current_frame.data >> 8) ) { - Dbprintf("!!! crc mismatch: expected %x but got %x !!!", - LegicCRC(byte_index, current_frame.data & 0xff, cmd_sz), current_frame.data >> 8); - return -1; - } - - return byte; -} - -/* legic_write_byte() is not included, however it's trivial to implement - * and here are some hints on what remains to be done: - * - * * assemble a write_cmd_frame with crc and send it - * * wait until the tag sends back an ACK ('1' bit unencrypted) - * * forward the prng based on the timing - */ -int legic_write_byte(int byte, int addr, int addr_sz) { - //do not write UID, CRC, DCF - if(addr <= 0x06) { - return 0; - } - - //== send write command ============================== - crc_clear(&legic_crc); - crc_update(&legic_crc, 0, 1); /* CMD_WRITE */ - crc_update(&legic_crc, addr, addr_sz); - crc_update(&legic_crc, byte, 8); - - uint32_t crc = crc_finish(&legic_crc); - uint32_t cmd = ((crc <<(addr_sz+1+8)) //CRC - |(byte <<(addr_sz+1)) //Data - |(addr <<1) //Address - |(0x00 <<0)); //CMD = W - uint32_t cmd_sz = addr_sz+1+8+4; //crc+data+cmd - - legic_prng_forward(2); /* we wait anyways */ - while(timer->TC_CV < 387) {}; /* ~ 258us */ - frame_send_rwd(cmd, cmd_sz); - - //== wait for ack ==================================== - int t, old_level=0, edges=0; - int next_bit_at =0; - while(timer->TC_CV < 387) ; /* ~ 258us */ - for(t=0; t<80; t++) { - edges = 0; - next_bit_at += TAG_TIME_BIT; - while(timer->TC_CV < next_bit_at) { - int level = (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - if(level != old_level) { - edges++; - } - old_level = level; - } - if(edges > 20 && edges < 60) { /* expected are 42 edges */ - int t = timer->TC_CV; - int c = t/TAG_TIME_BIT; - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1) ; /* Wait till the clock has reset */ - legic_prng_forward(c); - return 0; - } - } - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1) {}; /* Wait till the clock has reset */ - return -1; -} - -int LegicRfReader(int offset, int bytes) { - int byte_index=0, cmd_sz=0, card_sz=0; - - LegicCommonInit(); - - uint8_t *BigBuf = BigBuf_get_addr(); - memset(BigBuf, 0, 1024); - - DbpString("setting up legic card"); - uint32_t tag_type = perform_setup_phase_rwd(SESSION_IV); - switch_off_tag_rwd(); //we lose to mutch time with dprintf - switch(tag_type) { - case 0x1d: - DbpString("MIM 256 card found, reading card ..."); - cmd_sz = 9; - card_sz = 256; - break; - case 0x3d: - DbpString("MIM 1024 card found, reading card ..."); - cmd_sz = 11; - card_sz = 1024; - break; - default: - Dbprintf("Unknown card format: %x",tag_type); - return -1; - } - if(bytes == -1) { - bytes = card_sz; - } - if(bytes+offset >= card_sz) { - bytes = card_sz-offset; - } - - perform_setup_phase_rwd(SESSION_IV); - - LED_B_ON(); - while(byte_index < bytes) { - int r = legic_read_byte(byte_index+offset, cmd_sz); - if(r == -1 ||BUTTON_PRESS()) { - DbpString("operation aborted"); - switch_off_tag_rwd(); - LED_B_OFF(); - LED_C_OFF(); - return -1; - } - BigBuf[byte_index] = r; - WDT_HIT(); - byte_index++; - if(byte_index & 0x10) LED_C_ON(); else LED_C_OFF(); - } - LED_B_OFF(); - LED_C_OFF(); - switch_off_tag_rwd(); - Dbprintf("Card read, use 'hf legic decode' or"); - Dbprintf("'data hexsamples %d' to view results", (bytes+7) & ~7); - return 0; -} - -void LegicRfWriter(int bytes, int offset) { - int byte_index=0, addr_sz=0; - uint8_t *BigBuf = BigBuf_get_addr(); - - LegicCommonInit(); - - DbpString("setting up legic card"); - uint32_t tag_type = perform_setup_phase_rwd(SESSION_IV); - switch_off_tag_rwd(); - switch(tag_type) { - case 0x1d: - if(offset+bytes > 0x100) { - Dbprintf("Error: can not write to 0x%03.3x on MIM 256", offset+bytes); - return; - } - addr_sz = 8; - Dbprintf("MIM 256 card found, writing 0x%02.2x - 0x%02.2x ...", offset, offset+bytes); - break; - case 0x3d: - if(offset+bytes > 0x400) { - Dbprintf("Error: can not write to 0x%03.3x on MIM 1024", offset+bytes); - return; - } - addr_sz = 10; - Dbprintf("MIM 1024 card found, writing 0x%03.3x - 0x%03.3x ...", offset, offset+bytes); - break; - default: - Dbprintf("No or unknown card found, aborting"); - return; - } - - LED_B_ON(); - perform_setup_phase_rwd(SESSION_IV); - legic_prng_forward(2); - while(byte_index < bytes) { - int r = legic_write_byte(BigBuf[byte_index+offset], byte_index+offset, addr_sz); - if((r != 0) || BUTTON_PRESS()) { - Dbprintf("operation aborted @ 0x%03.3x", byte_index); - switch_off_tag_rwd(); - LED_B_OFF(); - LED_C_OFF(); - return; - } - WDT_HIT(); - byte_index++; - if(byte_index & 0x10) LED_C_ON(); else LED_C_OFF(); - } - LED_B_OFF(); - LED_C_OFF(); - DbpString("write successful"); -} - -int timestamp; - /* Handle (whether to respond) a frame in tag mode */ static void frame_handle_tag(struct legic_frame const * const f) { @@ -588,7 +665,7 @@ static void frame_handle_tag(struct legic_frame const * const f) 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 = LegicCRC(addr, data, 11) << 8; + int hash = calc_crc4(addr, data, 11) << 8; BigBuf[OFFSET_LOG+legic_read_count] = (uint8_t)addr; legic_read_count++; diff --git a/armsrc/legicrf.h b/armsrc/legicrf.h index 57ab7e6d..46459856 100644 --- a/armsrc/legicrf.h +++ b/armsrc/legicrf.h @@ -12,7 +12,7 @@ #define __LEGICRF_H extern void LegicRfSimulate(int phase, int frame, int reqresp); -extern int LegicRfReader(int bytes, int offset); +extern void LegicRfReader(int bytes, int offset); extern void LegicRfWriter(int bytes, int offset); #endif /* __LEGICRF_H */ diff --git a/fpga/hi_read_tx.v b/fpga/hi_read_tx.v index fc309cde..756683cd 100644 --- a/fpga/hi_read_tx.v +++ b/fpga/hi_read_tx.v @@ -71,21 +71,8 @@ always @(negedge ssp_clk) assign ssp_frame = (hi_byte_div == 3'b000); -// Implement a hysteresis to give out the received signal on -// ssp_din. Sample at fc. -assign adc_clk = ck_1356meg; +assign ssp_din = 1'b0; -// ADC data appears on the rising edge, so sample it on the falling edge -reg after_hysteresis; -always @(negedge adc_clk) -begin - if(& adc_d[7:0]) after_hysteresis <= 1'b1; - else if(~(| adc_d[7:0])) after_hysteresis <= 1'b0; -end +assign dbg = ssp_frame; - -assign ssp_din = after_hysteresis; - -assign dbg = ssp_din; - -endmodule +endmodule \ No newline at end of file diff --git a/include/legic.h b/include/legic.h new file mode 100644 index 00000000..246af0e8 --- /dev/null +++ b/include/legic.h @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// (c) 2016 Iceman +// +// 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 type prototyping +//----------------------------------------------------------------------------- + +#ifndef _LEGIC_H_ +#define _LEGIC_H_ + +#include "common.h" + +//----------------------------------------------------------------------------- +// LEGIC +//----------------------------------------------------------------------------- +typedef struct { + uint8_t uid[4]; + uint32_t tagtype; + uint8_t cmdsize; + uint8_t addrsize; + uint16_t cardsize; +} legic_card_select_t; + +#endif // _LEGIC_H_ From f6842317960fbf41cf981171789daebec8dbf803 Mon Sep 17 00:00:00 2001 From: AntiCat Date: Tue, 21 Aug 2018 05:08:06 +0200 Subject: [PATCH 3/4] Legic: fixed write (#655) Due to an oversight the bytes to be written were fetched from the wrong location. This is fixed now. --- armsrc/legicrf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index a611c741..2a236b6f 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -476,7 +476,7 @@ void LegicRfWriter(int bytes, int offset) { // write in reverse order, only then is DCF (decremental field) writable while(bytes-- > 0 && !BUTTON_PRESS()) { - if(!write_byte(bytes + offset, BigBuf[bytes], card.addrsize)) { + if(!write_byte(bytes + offset, BigBuf[bytes + offset], card.addrsize)) { Dbprintf("operation failed @ 0x%03.3x", bytes); goto OUT; } From 43591e6464af07466ffd87fcb970527ba748253a Mon Sep 17 00:00:00 2001 From: marshmellow42 Date: Mon, 20 Aug 2018 23:08:49 -0400 Subject: [PATCH 4/4] Add Smartcard functions (RDV4.0) (#646) * allow common makefile options-defines * remove non-existing file references * Uncomment lcd option (still) not enabled by default use Makefile_Enabled_Options.common to enable lcd if desired. * Add Smartcard Functions * add smartcard to menu + make get atr work sc is now functioning as far as my limited knowledge takes me * sc cleanup - add init to all sc commands... because cmds won't work until the first init happens. (multiple inits don't appear to affect it negatively) * default options to exclude Smartcard for main repo * update changelog --- CHANGELOG.md | 1 + armsrc/Makefile | 9 +- armsrc/appmain.c | 38 ++- armsrc/i2c.c | 720 ++++++++++++++++++++++++++++++++++++++++++ armsrc/i2c.h | 58 ++++ client/Makefile | 10 +- client/cmdhf.c | 6 + client/cmdmain.c | 7 +- client/cmdsmartcard.c | 707 +++++++++++++++++++++++++++++++++++++++++ client/cmdsmartcard.h | 39 +++ common/lfdemod.c | 4 +- common/protocols.h | 34 +- include/smartcard.h | 29 ++ include/usb_cmd.h | 21 +- 14 files changed, 1661 insertions(+), 22 deletions(-) create mode 100644 armsrc/i2c.c create mode 100644 armsrc/i2c.h create mode 100644 client/cmdsmartcard.c create mode 100644 client/cmdsmartcard.h create mode 100644 include/smartcard.h diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e2e045..da6463e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed driver file proxmark3.inf to support both old and new Product/Vendor IDs (piwi) ### Added +- Added `sc` smartcard (contact card) commands - reader, info, raw, upgrade, setclock, list (hardware version RDV4.0 only) must turn option on in makefile options (Willok, Iceman, marshmellow) - Added a bitbang mode to `lf cmdread` if delay is 0 the cmd bits turn off and on the antenna with 0 and 1 respectively (marshmellow) - Added PAC/Stanley detection to lf search (marshmellow) - Added lf pac demod and lf pac read - extracts the raw blocks from a PAC/Stanley tag (marshmellow) diff --git a/armsrc/Makefile b/armsrc/Makefile index f0a0c0ff..d4b13c6b 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -15,18 +15,22 @@ APP_CFLAGS = -DON_DEVICE \ include ../common/Makefile_Enabled_Options.common -ifneq (,$(findstring LCD,$(APP_CFLAGS))) +ifneq (,$(findstring WITH_LCD,$(APP_CFLAGS))) SRC_LCD = fonts.c LCD.c else SRC_LCD = endif -#SRC_LCD = fonts.c LCD.c SRC_LF = lfops.c hitag2.c hitagS.c lfsampling.c pcf7931.c lfdemod.c protocols.c SRC_ISO15693 = iso15693.c iso15693tools.c SRC_ISO14443a = epa.c iso14443a.c mifareutil.c mifarecmd.c mifaresniff.c mifaresim.c SRC_ISO14443b = iso14443b.c SRC_CRAPTO1 = crypto1.c des.c SRC_CRC = iso14443crc.c crc.c crc16.c crc32.c parity.c +ifneq (,$(findstring WITH_SMARTCARD,$(APP_CFLAGS))) + SRC_SMARTCARD = i2c.c +else + SRC_SMARTCARD = +endif #the FPGA bitstream files. Note: order matters! FPGA_BITSTREAMS = fpga_lf.bit fpga_hf.bit @@ -44,6 +48,7 @@ THUMBSRC = start.c \ $(SRC_ISO15693) \ $(SRC_LF) \ $(SRC_ZLIB) \ + $(SRC_SMARTCARD) \ appmain.c \ printf.c \ util.c \ diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 27f43b3f..4034788a 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -30,6 +30,10 @@ #ifdef WITH_LCD #include "LCD.h" #endif +#ifdef WITH_SMARTCARD + #include "i2c.h" +#endif + // Craig Young - 14a stand-alone code #ifdef WITH_ISO14443a @@ -357,12 +361,15 @@ void SendStatus(void) { BigBuf_print_status(); Fpga_print_status(); +#ifdef WITH_SMARTCARD + I2C_print_status(); +#endif printConfig(); //LF Sampling config printUSBSpeed(); Dbprintf("Various"); - Dbprintf(" MF_DBGLEVEL......%d", MF_DBGLEVEL); - Dbprintf(" ToSendMax........%d",ToSendMax); - Dbprintf(" ToSendBit........%d",ToSendBit); + Dbprintf(" MF_DBGLEVEL........%d", MF_DBGLEVEL); + Dbprintf(" ToSendMax..........%d", ToSendMax); + Dbprintf(" ToSendBit..........%d", ToSendBit); cmd_send(CMD_ACK,1,0,0,0,0); } @@ -1253,6 +1260,31 @@ void UsbPacketReceived(uint8_t *packet, int len) HfSnoop(c->arg[0], c->arg[1]); break; #endif +#ifdef WITH_SMARTCARD + case CMD_SMART_ATR: { + SmartCardAtr(); + break; + } + case CMD_SMART_SETCLOCK:{ + SmartCardSetClock(c->arg[0]); + break; + } + case CMD_SMART_RAW: { + SmartCardRaw(c->arg[0], c->arg[1], c->d.asBytes); + break; + } + case CMD_SMART_UPLOAD: { + // upload file from client + uint8_t *mem = BigBuf_get_addr(); + memcpy( mem + c->arg[0], c->d.asBytes, USB_CMD_DATA_SIZE); + cmd_send(CMD_ACK,1,0,0,0,0); + break; + } + case CMD_SMART_UPGRADE: { + SmartCardUpgrade(c->arg[0]); + break; + } +#endif case CMD_BUFF_CLEAR: BigBuf_Clear(); diff --git a/armsrc/i2c.c b/armsrc/i2c.c new file mode 100644 index 00000000..721b4b2e --- /dev/null +++ b/armsrc/i2c.c @@ -0,0 +1,720 @@ +//----------------------------------------------------------------------------- +// Willok, June 2018 +// Edits by Iceman, July 2018 +// +// 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. +//----------------------------------------------------------------------------- +// The main i2c code, for communications with smart card module +//----------------------------------------------------------------------------- +#include "i2c.h" +#include "mifareutil.h" //for mf_dbglevel +#include "string.h" //for memset memcmp + +// 定义连接引脚 +#define GPIO_RST AT91C_PIO_PA1 +#define GPIO_SCL AT91C_PIO_PA5 +#define GPIO_SDA AT91C_PIO_PA7 + +#define SCL_H HIGH(GPIO_SCL) +#define SCL_L LOW(GPIO_SCL) +#define SDA_H HIGH(GPIO_SDA) +#define SDA_L LOW(GPIO_SDA) + +#define SCL_read (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SCL) +#define SDA_read (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SDA) + +#define I2C_ERROR "I2C_WaitAck Error" + +volatile unsigned long c; + +// 直接使用循环来延时,一个循环 6 条指令,48M, Delay=1 大概为 200kbps +// timer. +// I2CSpinDelayClk(4) = 12.31us +// I2CSpinDelayClk(1) = 3.07us +void __attribute__((optimize("O0"))) I2CSpinDelayClk(uint16_t delay) { + for (c = delay * 2; c; c--) {}; +} + +// 通讯延迟函数 communication delay function +#define I2C_DELAY_1CLK I2CSpinDelayClk(1) +#define I2C_DELAY_2CLK I2CSpinDelayClk(2) +#define I2C_DELAY_XCLK(x) I2CSpinDelayClk((x)) + + +#define ISO7618_MAX_FRAME 255 + +void I2C_init(void) { + // 配置复位引脚,关闭上拉,推挽输出,默认高 + // Configure reset pin, close up pull up, push-pull output, default high + AT91C_BASE_PIOA->PIO_PPUDR = GPIO_RST; + AT91C_BASE_PIOA->PIO_MDDR = GPIO_RST; + + // 配置 I2C 引脚,开启上拉,开漏输出 + // Configure I2C pin, open up, open leakage + AT91C_BASE_PIOA->PIO_PPUER |= (GPIO_SCL | GPIO_SDA); // 打开上拉 Open up the pull up + AT91C_BASE_PIOA->PIO_MDER |= (GPIO_SCL | GPIO_SDA); + + // 默认三根线全部拉高 + // default three lines all pull up + AT91C_BASE_PIOA->PIO_SODR |= (GPIO_SCL | GPIO_SDA | GPIO_RST); + + // 允许输出 + // allow output + AT91C_BASE_PIOA->PIO_OER |= (GPIO_SCL | GPIO_SDA | GPIO_RST); + AT91C_BASE_PIOA->PIO_PER |= (GPIO_SCL | GPIO_SDA | GPIO_RST); +} + + +// 设置复位状态 +// set the reset state +void I2C_SetResetStatus(uint8_t LineRST, uint8_t LineSCK, uint8_t LineSDA) { + if (LineRST) + HIGH(GPIO_RST); + else + LOW(GPIO_RST); + + if (LineSCK) + HIGH(GPIO_SCL); + else + LOW(GPIO_SCL); + + if (LineSDA) + HIGH(GPIO_SDA); + else + LOW(GPIO_SDA); +} + +// 复位进入主程序 +// Reset the SIM_Adapter, then enter the main program +// Note: the SIM_Adapter will not enter the main program after power up. Please run this function before use SIM_Adapter. +void I2C_Reset_EnterMainProgram(void) { + I2C_SetResetStatus(0, 0, 0); // 拉低复位线 + SpinDelay(30); + I2C_SetResetStatus(1, 0, 0); // 解除复位 + SpinDelay(30); + I2C_SetResetStatus(1, 1, 1); // 拉高数据线 + SpinDelay(10); +} + +// 复位进入引导模式 +// Reset the SIM_Adapter, then enter the bootloader program +// Reserve:For firmware update. +void I2C_Reset_EnterBootloader(void) { + I2C_SetResetStatus(0, 1, 1); // 拉低复位线 + SpinDelay(100); + I2C_SetResetStatus(1, 1, 1); // 解除复位 + SpinDelay(10); +} + +// 等待时钟变高 +// Wait for the clock to go High. +bool WaitSCL_H_delay(uint32_t delay) { + while (delay--) { + if (SCL_read) { + return true; + } + I2C_DELAY_1CLK; + } + return false; +} + +// 5000 * 3.07us = 15350us. 15.35ms +bool WaitSCL_H(void) { + return WaitSCL_H_delay(5000); +} + +// Wait max 300ms or until SCL goes LOW. +// Which ever comes first +bool WaitSCL_L_300ms(void) { + volatile uint16_t delay = 300; + while ( delay-- ) { + // exit on SCL LOW + if (!SCL_read) + return true; + + SpinDelay(1); + } + return (delay == 0); +} + +bool I2C_Start(void) { + + I2C_DELAY_XCLK(4); + SDA_H; I2C_DELAY_1CLK; + SCL_H; + if (!WaitSCL_H()) return false; + + I2C_DELAY_2CLK; + + if (!SCL_read) return false; + if (!SDA_read) return false; + + SDA_L; I2C_DELAY_2CLK; + return true; +} + +bool I2C_WaitForSim() { + // variable delay here. + if (!WaitSCL_L_300ms()) + return false; + + // 8051 speaks with smart card. + // 1000*50*3.07 = 153.5ms + // 1byte transfer == 1ms + if (!WaitSCL_H_delay(2000*50) ) + return false; + + return true; +} + +// send i2c STOP +void I2C_Stop(void) { + SCL_L; I2C_DELAY_2CLK; + SDA_L; I2C_DELAY_2CLK; + SCL_H; I2C_DELAY_2CLK; + if (!WaitSCL_H()) return; + SDA_H; + I2C_DELAY_XCLK(8); +} + +// Send i2c ACK +void I2C_Ack(void) { + SCL_L; I2C_DELAY_2CLK; + SDA_L; I2C_DELAY_2CLK; + SCL_H; I2C_DELAY_2CLK; + SCL_L; I2C_DELAY_2CLK; +} + +// Send i2c NACK +void I2C_NoAck(void) { + SCL_L; I2C_DELAY_2CLK; + SDA_H; I2C_DELAY_2CLK; + SCL_H; I2C_DELAY_2CLK; + SCL_L; I2C_DELAY_2CLK; +} + +bool I2C_WaitAck(void) { + SCL_L; I2C_DELAY_1CLK; + SDA_H; I2C_DELAY_1CLK; + SCL_H; + if (!WaitSCL_H()) + return false; + + I2C_DELAY_2CLK; + if (SDA_read) { + SCL_L; + return false; + } + SCL_L; + return true; +} + +void I2C_SendByte(uint8_t data) { + uint8_t i = 8; + + while (i--) { + SCL_L; I2C_DELAY_1CLK; + + if (data & 0x80) + SDA_H; + else + SDA_L; + + data <<= 1; + I2C_DELAY_1CLK; + + SCL_H; + if (!WaitSCL_H()) + return; + + I2C_DELAY_2CLK; + } + SCL_L; +} + +uint8_t I2C_ReadByte(void) { + uint8_t i = 8, b = 0; + + SDA_H; + while (i--) { + b <<= 1; + SCL_L; I2C_DELAY_2CLK; + SCL_H; + if (!WaitSCL_H()) + return 0; + + I2C_DELAY_2CLK; + if (SDA_read) + b |= 0x01; + } + SCL_L; + return b; +} + +// Sends one byte ( command to be written, SlaveDevice address) +bool I2C_WriteCmd(uint8_t device_cmd, uint8_t device_address) { + bool bBreak = true; + do { + if (!I2C_Start()) + return false; + //[C0] + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + I2C_SendByte(device_cmd); + if (!I2C_WaitAck()) + break; + + bBreak = false; + } while (false); + + I2C_Stop(); + if (bBreak) { + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return false; + } + return true; +} + +// 写入1字节数据 (待写入数据,待写入地址,器件类型) +// Sends 1 byte data (Data to be written, command to be written , SlaveDevice address ). +bool I2C_WriteByte(uint8_t data, uint8_t device_cmd, uint8_t device_address) { + bool bBreak = true; + do { + if (!I2C_Start()) + return false; + + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + I2C_SendByte(device_cmd); + if (!I2C_WaitAck()) + break; + + I2C_SendByte(data); + if (!I2C_WaitAck()) + break; + + bBreak = false; + } while (false); + + I2C_Stop(); + if (bBreak) { + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return false; + } + return true; +} + +// 写入1串数据(待写入数组地址,待写入长度,待写入地址,器件类型) +//Sends a string of data (Array, length, command to be written , SlaveDevice address ). +// len = uint8 (max buffer to write 256bytes) +bool I2C_BufferWrite(uint8_t *data, uint8_t len, uint8_t device_cmd, uint8_t device_address) { + bool bBreak = true; + do { + if (!I2C_Start()) + return false; + + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + I2C_SendByte(device_cmd); + if (!I2C_WaitAck()) + break; + + while (len) { + + I2C_SendByte(*data); + if (!I2C_WaitAck()) + break; + + len--; + data++; + } + + if (len == 0) + bBreak = false; + } while (false); + + I2C_Stop(); + if (bBreak) { + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return false; + } + return true; +} + +// 读出1串数据(存放读出数据,待读出长度,带读出地址,器件类型) +// read 1 strings of data (Data array, Readout length, command to be written , SlaveDevice address ). +// len = uint8 (max buffer to read 256bytes) +uint8_t I2C_BufferRead(uint8_t *data, uint8_t len, uint8_t device_cmd, uint8_t device_address) { + + if ( !data || len == 0 ) + return 0; + + // extra wait 500us (514us measured) + // 200us (xx measured) + SpinDelayUs(200); + bool bBreak = true; + uint8_t readcount = 0; + + do { + if (!I2C_Start()) + return 0; + + // 0xB0 / 0xC0 == i2c write + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + I2C_SendByte(device_cmd); + if (!I2C_WaitAck()) + break; + + // 0xB1 / 0xC1 == i2c read + I2C_Start(); + I2C_SendByte(device_address | 1); + if (!I2C_WaitAck()) + break; + + bBreak = false; + } while (false); + + if (bBreak) { + I2C_Stop(); + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return 0; + } + + // reading + while (len) { + + *data = I2C_ReadByte(); + + len--; + + // 读取的第一个字节为后续长度 + // The first byte in response is the message length + if (!readcount && (len > *data)) { + len = *data; + } else { + data++; + } + readcount++; + + // acknowledgements. After last byte send NACK. + if (len == 0) + I2C_NoAck(); + else + I2C_Ack(); + } + + I2C_Stop(); + // return bytecount - first byte (which is length byte) + return (readcount) ? --readcount : 0; +} + +uint8_t I2C_ReadFW(uint8_t *data, uint8_t len, uint8_t msb, uint8_t lsb, uint8_t device_address) { + //START, 0xB0, 0x00, 0x00, START, 0xB1, xx, yy, zz, ......, STOP + bool bBreak = true; + uint8_t readcount = 0; + + // sending + do { + if (!I2C_Start()) + return 0; + + // 0xB0 / 0xC0 i2c write + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + // msb + I2C_SendByte(msb); + if (!I2C_WaitAck()) + break; + + // lsb + I2C_SendByte(lsb); + if (!I2C_WaitAck()) + break; + + // 0xB1 / 0xC1 i2c read + I2C_Start(); + I2C_SendByte(device_address | 1); + if (!I2C_WaitAck()) + break; + + bBreak = false; + } while (false); + + if (bBreak) { + I2C_Stop(); + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return 0; + } + + // reading + while (len) { + *data = I2C_ReadByte(); + + data++; + readcount++; + len--; + + // acknowledgements. After last byte send NACK. + if (len == 0) + I2C_NoAck(); + else + I2C_Ack(); + } + + I2C_Stop(); + return readcount; +} + +bool I2C_WriteFW(uint8_t *data, uint8_t len, uint8_t msb, uint8_t lsb, uint8_t device_address) { + //START, 0xB0, 0x00, 0x00, xx, yy, zz, ......, STOP + bool bBreak = true; + + do { + if (!I2C_Start()) + return false; + + // 0xB0 == i2c write + I2C_SendByte(device_address & 0xFE); + if (!I2C_WaitAck()) + break; + + // msb + I2C_SendByte(msb); + if (!I2C_WaitAck()) + break; + + // lsb + I2C_SendByte(lsb); + if (!I2C_WaitAck()) + break; + + while (len) { + I2C_SendByte(*data); + if (!I2C_WaitAck()) + break; + + len--; + data++; + } + + if (len == 0) + bBreak = false; + } while (false); + + I2C_Stop(); + if (bBreak) { + if ( MF_DBGLEVEL > 3 ) DbpString(I2C_ERROR); + return false; + } + return true; +} + +void I2C_print_status(void) { + DbpString("Smart card module (ISO 7816)"); + uint8_t resp[] = {0,0,0,0}; + I2C_init(); + I2C_Reset_EnterMainProgram(); + uint8_t len = I2C_BufferRead(resp, sizeof(resp), I2C_DEVICE_CMD_GETVERSION, I2C_DEVICE_ADDRESS_MAIN); + if ( len > 0 ) + Dbprintf(" version.................v%x.%02x", resp[0], resp[1]); + else + DbpString(" version.................FAILED"); +} + +bool GetATR(smart_card_atr_t *card_ptr) { + + // clear + if ( card_ptr ) { + card_ptr->atr_len = 0; + memset(card_ptr->atr, 0, sizeof(card_ptr->atr)); + } + + // Send ATR + // start [C0 01] stop start C1 len aa bb cc stop] + I2C_WriteCmd(I2C_DEVICE_CMD_GENERATE_ATR, I2C_DEVICE_ADDRESS_MAIN); + uint8_t cmd[1] = {1}; + LogTrace(cmd, 1, 0, 0, NULL, true); + + //wait for sim card to answer. + if (!I2C_WaitForSim()) + return false; + + // read answer + uint8_t len = I2C_BufferRead(card_ptr->atr, sizeof(card_ptr->atr), I2C_DEVICE_CMD_READ, I2C_DEVICE_ADDRESS_MAIN); + + if ( len == 0 ) + return false; + + // for some reason we only get first byte of atr, if that is so, send dummy command to retrieve the rest of the atr + if (len == 1) { + + uint8_t data[1] = {0}; + I2C_BufferWrite(data, len, I2C_DEVICE_CMD_SEND, I2C_DEVICE_ADDRESS_MAIN); + + if ( !I2C_WaitForSim() ) + return false; + + uint8_t len2 = I2C_BufferRead(card_ptr->atr + len, sizeof(card_ptr->atr) - len, I2C_DEVICE_CMD_READ, I2C_DEVICE_ADDRESS_MAIN); + len = len + len2; + } + + if ( card_ptr ) { + card_ptr->atr_len = len; + LogTrace(card_ptr->atr, card_ptr->atr_len, 0, 0, NULL, false); + } + + return true; +} + +void SmartCardAtr(void) { + smart_card_atr_t card; + LED_D_ON(); + clear_trace(); + set_tracing(true); + I2C_init(); + I2C_Reset_EnterMainProgram(); + bool isOK = GetATR( &card ); + cmd_send(CMD_ACK, isOK, sizeof(smart_card_atr_t), 0, &card, sizeof(smart_card_atr_t)); + set_tracing(false); + LEDsoff(); +} + +void SmartCardRaw( uint64_t arg0, uint64_t arg1, uint8_t *data ) { + + LED_D_ON(); + + uint8_t len = 0; + uint8_t *resp = BigBuf_malloc(ISO7618_MAX_FRAME); + smartcard_command_t flags = arg0; + + if ((flags & SC_CONNECT)) + clear_trace(); + + set_tracing(true); + + if ((flags & SC_CONNECT)) { + + I2C_init(); + I2C_Reset_EnterMainProgram(); + + if ( !(flags & SC_NO_SELECT) ) { + smart_card_atr_t card; + bool gotATR = GetATR( &card ); + //cmd_send(CMD_ACK, gotATR, sizeof(smart_card_atr_t), 0, &card, sizeof(smart_card_atr_t)); + if ( !gotATR ) + goto OUT; + } + } + + if ((flags & SC_RAW)) { + + LogTrace(data, arg1, 0, 0, NULL, true); + + // Send raw bytes + // asBytes = A0 A4 00 00 02 + // arg1 = len 5 + I2C_BufferWrite(data, arg1, I2C_DEVICE_CMD_SEND, I2C_DEVICE_ADDRESS_MAIN); + + if ( !I2C_WaitForSim() ) + goto OUT; + + len = I2C_BufferRead(resp, ISO7618_MAX_FRAME, I2C_DEVICE_CMD_READ, I2C_DEVICE_ADDRESS_MAIN); + LogTrace(resp, len, 0, 0, NULL, false); + } +OUT: + cmd_send(CMD_ACK, len, 0, 0, resp, len); + set_tracing(false); + LEDsoff(); +} + +void SmartCardUpgrade(uint64_t arg0) { + + LED_C_ON(); + + #define I2C_BLOCK_SIZE 128 + // write. Sector0, with 11,22,33,44 + // erase is 128bytes, and takes 50ms to execute + + I2C_init(); + I2C_Reset_EnterBootloader(); + + bool isOK = true; + uint8_t res = 0; + uint16_t length = arg0; + uint16_t pos = 0; + uint8_t *fwdata = BigBuf_get_addr(); + uint8_t *verfiydata = BigBuf_malloc(I2C_BLOCK_SIZE); + + while (length) { + + uint8_t msb = (pos >> 8) & 0xFF; + uint8_t lsb = pos & 0xFF; + + Dbprintf("FW %02X%02X", msb, lsb); + + size_t size = MIN(I2C_BLOCK_SIZE, length); + + // write + res = I2C_WriteFW(fwdata+pos, size, msb, lsb, I2C_DEVICE_ADDRESS_BOOT); + if ( !res ) { + DbpString("Writing failed"); + isOK = false; + break; + } + + // writing takes time. + SpinDelay(50); + + // read + res = I2C_ReadFW(verfiydata, size, msb, lsb, I2C_DEVICE_ADDRESS_BOOT); + if ( res == 0) { + DbpString("Reading back failed"); + isOK = false; + break; + } + + // cmp + if ( 0 != memcmp(fwdata+pos, verfiydata, size)) { + DbpString("not equal data"); + isOK = false; + break; + } + + length -= size; + pos += size; + } + cmd_send(CMD_ACK, isOK, pos, 0, 0, 0); + LED_C_OFF(); +} + +// unfinished (or not needed?) +//void SmartCardSetBaud(uint64_t arg0) { +//} + +void SmartCardSetClock(uint64_t arg0) { + LED_D_ON(); + set_tracing(true); + I2C_init(); + I2C_Reset_EnterMainProgram(); + + // Send SIM CLC + // start [C0 05 xx] stop + I2C_WriteByte(arg0, I2C_DEVICE_CMD_SIM_CLC, I2C_DEVICE_ADDRESS_MAIN); + + cmd_send(CMD_ACK, 1, 0, 0, 0, 0); + set_tracing(false); + LEDsoff(); +} diff --git a/armsrc/i2c.h b/armsrc/i2c.h new file mode 100644 index 00000000..4c5c5228 --- /dev/null +++ b/armsrc/i2c.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Willok, June 2018 +// Edits by Iceman, July 2018 +// +// 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. +//----------------------------------------------------------------------------- +// The main i2c code, for communications with smart card module +//----------------------------------------------------------------------------- +#ifndef __I2C_H +#define __I2C_H + +#include +#include "proxmark3.h" +#include "apps.h" +#include "util.h" +#include "BigBuf.h" +#include "smartcard.h" + +#define I2C_DEVICE_ADDRESS_BOOT 0xB0 +#define I2C_DEVICE_ADDRESS_MAIN 0xC0 + +#define I2C_DEVICE_CMD_GENERATE_ATR 0x01 +#define I2C_DEVICE_CMD_SEND 0x02 +#define I2C_DEVICE_CMD_READ 0x03 +#define I2C_DEVICE_CMD_SETBAUD 0x04 +#define I2C_DEVICE_CMD_SIM_CLC 0x05 +#define I2C_DEVICE_CMD_GETVERSION 0x06 + + +void I2C_init(void); +void I2C_Reset(void); +void I2C_SetResetStatus(uint8_t LineRST, uint8_t LineSCK, uint8_t LineSDA); + +void I2C_Reset_EnterMainProgram(void); +void I2C_Reset_EnterBootloader(void); + +bool I2C_WriteCmd(uint8_t device_cmd, uint8_t device_address); + +bool I2C_WriteByte(uint8_t data, uint8_t device_cmd, uint8_t device_address); +bool I2C_BufferWrite(uint8_t *data, uint8_t len, uint8_t device_cmd, uint8_t device_address); +uint8_t I2C_BufferRead(uint8_t *data, uint8_t len, uint8_t device_cmd, uint8_t device_address); + +// for firmware +uint8_t I2C_ReadFW(uint8_t *data, uint8_t len, uint8_t msb, uint8_t lsb, uint8_t device_address); +bool I2C_WriteFW(uint8_t *data, uint8_t len, uint8_t msb, uint8_t lsb, uint8_t device_address); + +bool GetATR(smart_card_atr_t *card_ptr); + +// generic functions +void SmartCardAtr(void); +void SmartCardRaw(uint64_t arg0, uint64_t arg1, uint8_t *data); +void SmartCardUpgrade(uint64_t arg0); +//void SmartCardSetBaud(uint64_t arg0); +void SmartCardSetClock(uint64_t arg0); +void I2C_print_status(void); +#endif diff --git a/client/Makefile b/client/Makefile index 2d256b72..c6ca1cf1 100644 --- a/client/Makefile +++ b/client/Makefile @@ -26,6 +26,11 @@ CXXFLAGS = -I../include -Wall -O3 APP_CFLAGS = include ../common/Makefile_Enabled_Options.common CFLAGS += $(APP_CFLAGS) +ifneq (,$(findstring WITH_SMARTCARD,$(APP_CFLAGS))) + SRC_SMARTCARD = cmdsmartcard.c +else + SRC_SMARTCARD = +endif LUAPLATFORM = generic platform = $(shell uname) @@ -93,7 +98,8 @@ CORESRCS = uart_posix.c \ ui.c \ comms.c -CMDSRCS = crapto1/crapto1.c\ +CMDSRCS = $(SRC_SMARTCARD) \ + crapto1/crapto1.c\ crapto1/crypto1.c\ polarssl/des.c \ polarssl/aes.c\ @@ -310,9 +316,7 @@ DEPENDENCY_FILES = $(patsubst %.c, $(OBJDIR)/%.d, $(CORESRCS) $(CMDSRCS) $(ZLIBS $(patsubst %.cpp, $(OBJDIR)/%.d, $(QTGUISRCS)) \ $(OBJDIR)/proxmark3.d $(OBJDIR)/flash.d $(OBJDIR)/flasher.d $(OBJDIR)/fpga_compress.d - $(DEPENDENCY_FILES): ; .PRECIOUS: $(DEPENDENCY_FILES) -include $(DEPENDENCY_FILES) - diff --git a/client/cmdhf.c b/client/cmdhf.c index 93906a7d..b973354d 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -353,6 +353,12 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui int CmdHFList(const char *Cmd) { + #ifdef WITH_SMARTCARD + PrintAndLog("TEST_WITH_SMARTCARD"); + #endif + #ifdef WITH_TEST + PrintAndLog("TEST_WITH_TEST"); + #endif bool showWaitCycles = false; bool markCRCBytes = false; bool loadFromFile = false; diff --git a/client/cmdmain.c b/client/cmdmain.c index 01d4c9a7..f503021a 100644 --- a/client/cmdmain.c +++ b/client/cmdmain.c @@ -26,7 +26,9 @@ #include "util.h" #include "util_posix.h" #include "cmdscript.h" - +#ifdef WITH_SMARTCARD + #include "cmdsmartcard.h" +#endif static int CmdHelp(const char *Cmd); static int CmdQuit(const char *Cmd); @@ -39,6 +41,9 @@ static command_t CommandTable[] = {"hf", CmdHF, 1, "{ High Frequency commands... }"}, {"hw", CmdHW, 1, "{ Hardware commands... }"}, {"lf", CmdLF, 1, "{ Low Frequency commands... }"}, +#ifdef WITH_SMARTCARD + {"sc", CmdSmartcard,1,"{ Smartcard commands... }"}, +#endif {"script",CmdScript,1, "{ Scripting commands }"}, {"quit", CmdQuit, 1, "Exit program"}, {"exit", CmdQuit, 1, "Exit program"}, diff --git a/client/cmdsmartcard.c b/client/cmdsmartcard.c new file mode 100644 index 00000000..b2a5705d --- /dev/null +++ b/client/cmdsmartcard.c @@ -0,0 +1,707 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 iceman +// +// 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. +//----------------------------------------------------------------------------- +// Proxmark3 RDV40 Smartcard module commands +//----------------------------------------------------------------------------- +#include "cmdsmartcard.h" +#include "smartcard.h" +#include "comms.h" +#include "protocols.h" + + +static int CmdHelp(const char *Cmd); + +int usage_sm_raw(void) { + PrintAndLog("Usage: sc raw [h|r|c] d <0A 0B 0C ... hex>"); + PrintAndLog(" h : this help"); + PrintAndLog(" r : do not read response"); + PrintAndLog(" a : active signal field ON without select"); + PrintAndLog(" s : active signal field ON with select"); + PrintAndLog(" t : executes TLV decoder if it is possible"); + PrintAndLog(" d : bytes to send"); + PrintAndLog(""); + PrintAndLog("Examples:"); + PrintAndLog(" sc raw d 11223344"); + return 0; +} +int usage_sm_reader(void) { + PrintAndLog("Usage: sc reader [h|s]"); + PrintAndLog(" h : this help"); + PrintAndLog(" s : silent (no messages)"); + PrintAndLog(""); + PrintAndLog("Examples:"); + PrintAndLog(" sc reader"); + return 0; +} +int usage_sm_info(void) { + PrintAndLog("Usage: sc info [h|s]"); + PrintAndLog(" h : this help"); + PrintAndLog(" s : silent (no messages)"); + PrintAndLog(""); + PrintAndLog("Examples:"); + PrintAndLog(" sc info"); + return 0; +} +int usage_sm_upgrade(void) { + PrintAndLog("Upgrade firmware"); + PrintAndLog("Usage: sc upgrade f "); + PrintAndLog(" h : this help"); + PrintAndLog(" f : firmware file name"); + PrintAndLog(""); + PrintAndLog("Examples:"); + PrintAndLog(" sc upgrade f myfile"); + PrintAndLog(""); + PrintAndLog("WARNING - Dangerous command, do wrong and you will brick the smart card socket"); + return 0; +} +int usage_sm_setclock(void) { + PrintAndLog("Usage: sc setclock [h] c "); + PrintAndLog(" h : this help"); + PrintAndLog(" c <> : clockspeed (0 = 16mhz, 1=8mhz, 2=4mhz) "); + PrintAndLog(""); + PrintAndLog("Examples:"); + PrintAndLog(" sc setclock c 2"); + return 0; +} + +int CmdSmartRaw(const char *Cmd) { + + int hexlen = 0; + bool active = false; + bool active_select = false; + uint8_t cmdp = 0; + bool errors = false, reply = true, decodeTLV = false, breakloop = false; + uint8_t data[USB_CMD_DATA_SIZE] = {0x00}; + + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + case 'h': return usage_sm_raw(); + case 'r': + reply = false; + cmdp++; + break; + case 'a': + active = true; + cmdp++; + break; + case 's': + active_select = true; + cmdp++; + break; + case 't': + decodeTLV = true; + cmdp++; + break; + case 'd': { + switch (param_gethex_to_eol(Cmd, cmdp+1, data, sizeof(data), &hexlen)) { + case 1: + PrintAndLog("Invalid HEX value."); + return 1; + case 2: + PrintAndLog("Too many bytes. Max %d bytes", sizeof(data)); + return 1; + case 3: + PrintAndLog("Hex must have an even number of digits."); + return 1; + } + cmdp++; + breakloop = true; + break; + } + default: + PrintAndLog("Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + + if ( breakloop ) + break; + } + + //Validations + if (errors || cmdp == 0 ) return usage_sm_raw(); + + // arg0 = RFU flags + // arg1 = length + UsbCommand c = {CMD_SMART_RAW, {0, hexlen, 0}}; + + if (active || active_select) { + c.arg[0] |= SC_CONNECT; + if (active) + c.arg[0] |= SC_NO_SELECT; + } + + if (hexlen > 0) { + c.arg[0] |= SC_RAW; + } + + memcpy(c.d.asBytes, data, hexlen ); + clearCommandBuffer(); + SendCommand(&c); + + // reading response from smart card + if ( reply ) { + UsbCommand resp; + if (!WaitForResponseTimeout(CMD_ACK, &resp, 2500)) { + PrintAndLog("smart card response failed"); + return 1; + } + uint32_t datalen = resp.arg[0]; + + if ( !datalen ) { + PrintAndLog("smart card response failed"); + return 1; + } + + PrintAndLog("received %i bytes", datalen); + + if (!datalen) + return 1; + + uint8_t *data = resp.d.asBytes; + + // TLV decoder + if (decodeTLV ) { + + if (datalen >= 2) { + PrintAndLog("%02x %02x | %s", data[datalen - 2], data[datalen - 1], GetAPDUCodeDescription(data[datalen - 2], data[datalen - 1])); + } + if (datalen > 4) { + TLVPrintFromBuffer(data, datalen - 2); + } + } else { + PrintAndLog("%s", sprint_hex(data, datalen)); + } + } + return 0; +} + +int CmdSmartUpgrade(const char *Cmd) { + + PrintAndLog("WARNING - Smartcard socket firmware upgrade."); + PrintAndLog("Dangerous command, do wrong and you will brick the smart card socket"); + + FILE *f; + char filename[FILE_PATH_SIZE] = {0}; + uint8_t cmdp = 0; + bool errors = false; + + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + case 'f': + //File handling and reading + if ( param_getstr(Cmd, cmdp+1, filename, FILE_PATH_SIZE) >= FILE_PATH_SIZE ) { + PrintAndLog("Filename too long"); + errors = true; + break; + } + cmdp += 2; + break; + case 'h': + return usage_sm_upgrade(); + default: + PrintAndLog("Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + } + + //Validations + if (errors || cmdp == 0 ) return usage_sm_upgrade(); + + // load file + f = fopen(filename, "rb"); + if ( !f ) { + PrintAndLog("File: %s: not found or locked.", filename); + return 1; + } + + // get filesize in order to malloc memory + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + if (fsize < 0) { + PrintAndLog("error, when getting filesize"); + fclose(f); + return 1; + } + + uint8_t *dump = calloc(fsize, sizeof(uint8_t)); + if (!dump) { + PrintAndLog("error, cannot allocate memory "); + fclose(f); + return 1; + } + + size_t bytes_read = fread(dump, 1, fsize, f); + if (f) + fclose(f); + + PrintAndLog("Smartcard socket firmware uploading to PM3"); + //Send to device + uint32_t index = 0; + uint32_t bytes_sent = 0; + uint32_t bytes_remaining = bytes_read; + + while (bytes_remaining > 0){ + uint32_t bytes_in_packet = MIN(USB_CMD_DATA_SIZE, bytes_remaining); + UsbCommand c = {CMD_SMART_UPLOAD, {index + bytes_sent, bytes_in_packet, 0}}; + + // Fill usb bytes with 0xFF + memset(c.d.asBytes, 0xFF, USB_CMD_DATA_SIZE); + memcpy(c.d.asBytes, dump + bytes_sent, bytes_in_packet); + clearCommandBuffer(); + SendCommand(&c); + if ( !WaitForResponseTimeout(CMD_ACK, NULL, 2000) ) { + PrintAndLog("timeout while waiting for reply."); + free(dump); + return 1; + } + + bytes_remaining -= bytes_in_packet; + bytes_sent += bytes_in_packet; + printf("."); fflush(stdout); + } + free(dump); + printf("\n"); + PrintAndLog("Smartcard socket firmware updating, don\'t turn off your PM3!"); + + // trigger the firmware upgrade + UsbCommand c = {CMD_SMART_UPGRADE, {bytes_read, 0, 0}}; + clearCommandBuffer(); + SendCommand(&c); + UsbCommand resp; + if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { + PrintAndLog("timeout while waiting for reply."); + return 1; + } + if ( (resp.arg[0] && 0xFF ) ) + PrintAndLog("Smartcard socket firmware upgraded successful"); + else + PrintAndLog("Smartcard socket firmware updating failed"); + return 0; +} + +int CmdSmartInfo(const char *Cmd){ + uint8_t cmdp = 0; + bool errors = false, silent = false; + + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + case 'h': return usage_sm_info(); + case 's': + silent = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + cmdp++; + } + + //Validations + if (errors ) return usage_sm_info(); + + UsbCommand c = {CMD_SMART_ATR, {0, 0, 0}}; + clearCommandBuffer(); + SendCommand(&c); + UsbCommand resp; + if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { + if (!silent) PrintAndLog("smart card select failed"); + return 1; + } + + uint8_t isok = resp.arg[0] & 0xFF; + if (!isok) { + if (!silent) PrintAndLog("smart card select failed"); + return 1; + } + + smart_card_atr_t card; + memcpy(&card, (smart_card_atr_t *)resp.d.asBytes, sizeof(smart_card_atr_t)); + + // print header + PrintAndLog("\n--- Smartcard Information ---------"); + PrintAndLog("-------------------------------------------------------------"); + PrintAndLog("ISO76183 ATR : %s", sprint_hex(card.atr, card.atr_len)); + PrintAndLog("look up ATR"); + PrintAndLog("http://smartcard-atr.appspot.com/parse?ATR=%s", sprint_hex_inrow(card.atr, card.atr_len) ); + return 0; +} + +int CmdSmartReader(const char *Cmd){ + uint8_t cmdp = 0; + bool errors = false, silent = false; + + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + case 'h': return usage_sm_reader(); + case 's': + silent = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + cmdp++; + } + + //Validations + if (errors ) return usage_sm_reader(); + + UsbCommand c = {CMD_SMART_ATR, {0, 0, 0}}; + clearCommandBuffer(); + SendCommand(&c); + UsbCommand resp; + if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { + if (!silent) PrintAndLog("smart card select failed"); + return 1; + } + + uint8_t isok = resp.arg[0] & 0xFF; + if (!isok) { + if (!silent) PrintAndLog("smart card select failed"); + return 1; + } + smart_card_atr_t card; + memcpy(&card, (smart_card_atr_t *)resp.d.asBytes, sizeof(smart_card_atr_t)); + PrintAndLog("ISO7816-3 ATR : %s", sprint_hex(card.atr, card.atr_len)); + return 0; +} + +int CmdSmartSetClock(const char *Cmd){ + uint8_t cmdp = 0; + bool errors = false; + uint8_t clock = 0; + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + case 'h': return usage_sm_setclock(); + case 'c': + clock = param_get8ex(Cmd, cmdp+1, 2, 10); + if ( clock > 2) + errors = true; + + cmdp += 2; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + } + + //Validations + if (errors || cmdp == 0) return usage_sm_setclock(); + + UsbCommand c = {CMD_SMART_SETCLOCK, {clock, 0, 0}}; + clearCommandBuffer(); + SendCommand(&c); + UsbCommand resp; + if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { + PrintAndLog("smart card select failed"); + return 1; + } + + uint8_t isok = resp.arg[0] & 0xFF; + if (!isok) { + PrintAndLog("smart card set clock failed"); + return 1; + } + + switch (clock) { + case 0: + PrintAndLog("Clock changed to 16mhz giving 10800 baudrate"); + break; + case 1: + PrintAndLog("Clock changed to 8mhz giving 21600 baudrate"); + break; + case 2: + PrintAndLog("Clock changed to 4mhz giving 86400 baudrate"); + break; + default: + break; + } + return 0; +} + + +// iso 7816-3 +void annotateIso7816(char *exp, size_t size, uint8_t* cmd, uint8_t cmdsize){ + // S-block + if ( (cmd[0] & 0xC0) && (cmdsize == 3) ) { + switch ( (cmd[0] & 0x3f) ) { + case 0x00 : snprintf(exp, size, "S-block RESYNCH req"); break; + case 0x20 : snprintf(exp, size, "S-block RESYNCH resp"); break; + case 0x01 : snprintf(exp, size, "S-block IFS req"); break; + case 0x21 : snprintf(exp, size, "S-block IFS resp"); break; + case 0x02 : snprintf(exp, size, "S-block ABORT req"); break; + case 0x22 : snprintf(exp, size, "S-block ABORT resp"); break; + case 0x03 : snprintf(exp, size, "S-block WTX reqt"); break; + case 0x23 : snprintf(exp, size, "S-block WTX resp"); break; + default : snprintf(exp, size, "S-block"); break; + } + } + // R-block (ack) + else if ( ((cmd[0] & 0xD0) == 0x80) && ( cmdsize > 2) ) { + if ( (cmd[0] & 0x10) == 0 ) + snprintf(exp, size, "R-block ACK"); + else + snprintf(exp, size, "R-block NACK"); + } + // I-block + else { + + int pos = (cmd[0] == 2 || cmd[0] == 3) ? 2 : 3; + switch ( cmd[pos] ) { + case ISO7816_READ_BINARY :snprintf(exp, size, "READ BIN");break; + case ISO7816_WRITE_BINARY :snprintf(exp, size, "WRITE BIN");break; + case ISO7816_UPDATE_BINARY :snprintf(exp, size, "UPDATE BIN");break; + case ISO7816_ERASE_BINARY :snprintf(exp, size, "ERASE BIN");break; + case ISO7816_READ_RECORDS :snprintf(exp, size, "READ RECORDS");break; + case ISO7816_WRITE_RECORDS :snprintf(exp, size, "WRITE RECORDS");break; + case ISO7816_APPEND_RECORD :snprintf(exp, size, "APPEND RECORD");break; + case ISO7816_UPDATE_RECORD :snprintf(exp, size, "UPDATE RECORD");break; + case ISO7816_GET_DATA :snprintf(exp, size, "GET DATA");break; + case ISO7816_PUT_DATA :snprintf(exp, size, "PUT DATA");break; + case ISO7816_SELECT_FILE :snprintf(exp, size, "SELECT FILE");break; + case ISO7816_VERIFY :snprintf(exp, size, "VERIFY");break; + case ISO7816_INTERNAL_AUTHENTICATION :snprintf(exp, size, "INTERNAL AUTH");break; + case ISO7816_EXTERNAL_AUTHENTICATION :snprintf(exp, size, "EXTERNAL AUTH");break; + case ISO7816_GET_CHALLENGE :snprintf(exp, size, "GET CHALLENGE");break; + case ISO7816_MANAGE_CHANNEL :snprintf(exp, size, "MANAGE CHANNEL");break; + default :snprintf(exp, size, "?"); break; + } + } +} + + +uint16_t printScTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace) { + // sanity check + if (tracepos + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t) > traceLen) return traceLen; + + bool isResponse; + uint16_t data_len, parity_len; + uint32_t duration, timestamp, first_timestamp, EndOfTransmissionTimestamp; + char explanation[30] = {0}; + + first_timestamp = *((uint32_t *)(trace)); + timestamp = *((uint32_t *)(trace + tracepos)); + tracepos += 4; + + duration = *((uint16_t *)(trace + tracepos)); + tracepos += 2; + + data_len = *((uint16_t *)(trace + tracepos)); + tracepos += 2; + + if (data_len & 0x8000) { + data_len &= 0x7fff; + isResponse = true; + } else { + isResponse = false; + } + + parity_len = (data_len-1)/8 + 1; + if (tracepos + data_len + parity_len > traceLen) { + return traceLen; + } + uint8_t *frame = trace + tracepos; + tracepos += data_len; + //uint8_t *parityBytes = trace + tracepos; + tracepos += parity_len; + + //--- Draw the data column + char line[18][110]; + + if (data_len == 0 ) { + sprintf(line[0],""); + return tracepos; + } + + for (int j = 0; j < data_len && j/18 < 18; j++) { + snprintf(line[j/18]+(( j % 18) * 4),110, "%02x ", frame[j]); + } + + EndOfTransmissionTimestamp = timestamp + duration; + + annotateIso7816(explanation,sizeof(explanation),frame,data_len); + + int num_lines = MIN((data_len - 1)/18 + 1, 18); + for (int j = 0; j < num_lines ; j++) { + if (j == 0) { + PrintAndLog(" %10u | %10u | %s |%-72s | %s| %s", + (timestamp - first_timestamp), + (EndOfTransmissionTimestamp - first_timestamp), + (isResponse ? "Tag" : "Rdr"), + line[j], + " ", + (j == num_lines-1) ? explanation : ""); + } else { + PrintAndLog(" | | |%-72s | %s| %s", + line[j], + " ", + (j == num_lines-1) ? explanation : ""); + } + } + + // if is last record + if (tracepos + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t) >= traceLen) return traceLen; + + return tracepos; +} + +int ScTraceList(const char *Cmd) { + bool loadFromFile = false; + bool saveToFile = false; + char type[5] = {0}; + char filename[FILE_PATH_SIZE] = {0}; + + // parse command line + param_getstr(Cmd, 0, type, sizeof(type)); + param_getstr(Cmd, 1, filename, sizeof(filename)); + + bool errors = false; + if(type[0] == 'h') { + errors = true; + } + + if(!errors) { + if (strcmp(type, "s") == 0) { + saveToFile = true; + } else if (strcmp(type,"l") == 0) { + loadFromFile = true; + } + } + + if ((loadFromFile || saveToFile) && strlen(filename) == 0) { + errors = true; + } + + if (loadFromFile && saveToFile) { + errors = true; + } + + if (errors) { + PrintAndLog("List or save protocol data."); + PrintAndLog("Usage: sc list [l ]"); + PrintAndLog(" sc list [s ]"); + PrintAndLog(" l - load data from file instead of trace buffer"); + PrintAndLog(" s - save data to file"); + PrintAndLog(""); + PrintAndLog("example: sc list"); + PrintAndLog("example: sc list save myCardTrace.trc"); + PrintAndLog("example: sc list l myCardTrace.trc"); + return 0; + } + + uint8_t *trace; + uint32_t tracepos = 0; + uint32_t traceLen = 0; + + if (loadFromFile) { + #define TRACE_CHUNK_SIZE (1<<16) // 64K to start with. Will be enough for BigBuf and some room for future extensions + FILE *tracefile = NULL; + size_t bytes_read; + trace = malloc(TRACE_CHUNK_SIZE); + if (trace == NULL) { + PrintAndLog("Cannot allocate memory for trace"); + return 2; + } + if ((tracefile = fopen(filename,"rb")) == NULL) { + PrintAndLog("Could not open file %s", filename); + free(trace); + return 0; + } + while (!feof(tracefile)) { + bytes_read = fread(trace+traceLen, 1, TRACE_CHUNK_SIZE, tracefile); + traceLen += bytes_read; + if (!feof(tracefile)) { + uint8_t *p = realloc(trace, traceLen + TRACE_CHUNK_SIZE); + if (p == NULL) { + PrintAndLog("Cannot allocate memory for trace"); + free(trace); + fclose(tracefile); + return 2; + } + trace = p; + } + } + fclose(tracefile); + } else { + trace = malloc(USB_CMD_DATA_SIZE); + // Query for the size of the trace + UsbCommand response; + GetFromBigBuf(trace, USB_CMD_DATA_SIZE, 0, &response, -1, false); + traceLen = response.arg[2]; + if (traceLen > USB_CMD_DATA_SIZE) { + uint8_t *p = realloc(trace, traceLen); + if (p == NULL) { + PrintAndLog("Cannot allocate memory for trace"); + free(trace); + return 2; + } + trace = p; + GetFromBigBuf(trace, traceLen, 0, NULL, -1, false); + } + } + + if (saveToFile) { + FILE *tracefile = NULL; + if ((tracefile = fopen(filename,"wb")) == NULL) { + PrintAndLog("Could not create file %s", filename); + return 1; + } + fwrite(trace, 1, traceLen, tracefile); + PrintAndLog("Recorded Activity (TraceLen = %d bytes) written to file %s", traceLen, filename); + fclose(tracefile); + } else { + PrintAndLog("Recorded Activity (TraceLen = %d bytes)", traceLen); + PrintAndLog(""); + PrintAndLog("Start = Start of Start Bit, End = End of last modulation. Src = Source of Transfer"); + PrintAndLog(""); + PrintAndLog(" Start | End | Src | Data (! denotes parity error) | CRC | Annotation |"); + PrintAndLog("------------|------------|-----|-------------------------------------------------------------------------|-----|--------------------|"); + + while(tracepos < traceLen) + { + tracepos = printScTraceLine(tracepos, traceLen, trace); + } + } + + free(trace); + return 0; +} + +int CmdSmartList(const char *Cmd) { + ScTraceList(Cmd); + return 0; +} + +static command_t CommandTable[] = { + {"help", CmdHelp, 1, "This help"}, + {"list", CmdSmartList, 0, "List ISO 7816 history"}, + {"info", CmdSmartInfo, 1, "Tag information [rdv40]"}, + {"reader", CmdSmartReader, 1, "Act like an IS07816 reader [rdv40]"}, + {"raw", CmdSmartRaw, 1, "Send raw hex data to tag [rdv40]"}, + {"upgrade", CmdSmartUpgrade, 1, "Upgrade firmware [rdv40]"}, + {"setclock",CmdSmartSetClock, 1, "Set clock speed"}, + {NULL, NULL, 0, NULL} +}; + +int CmdSmartcard(const char *Cmd) { + clearCommandBuffer(); + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +} diff --git a/client/cmdsmartcard.h b/client/cmdsmartcard.h new file mode 100644 index 00000000..caa06f4f --- /dev/null +++ b/client/cmdsmartcard.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 iceman +// +// 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. +//----------------------------------------------------------------------------- +// Proxmark3 RDV40 Smartcard module commands +//----------------------------------------------------------------------------- + +#ifndef CMDSMARTCARD_H__ +#define CMDSMARTCARD_H__ + +#include +#include +#include +#include +#include "proxmark3.h" +#include "ui.h" +#include "cmdparser.h" +#include "common.h" +#include "util.h" +#include "loclass/fileutils.h" // saveFile +#include "cmdmain.h" // getfromdevice +#include "emv/emvcore.h" // decodeTVL +#include "emv/apduinfo.h" // APDUcode description + +extern int CmdSmartcard(const char *Cmd); + +extern int CmdSmartRaw(const char* cmd); +extern int CmdSmartUpgrade(const char* cmd); +extern int CmdSmartInfo(const char* cmd); +extern int CmdSmartReader(const char *Cmd); + +extern int usage_sm_raw(void); +extern int usage_sm_reader(void); +extern int usage_sm_info(void); +extern int usage_sm_upgrade(void); +#endif diff --git a/common/lfdemod.c b/common/lfdemod.c index f470371a..76900047 100644 --- a/common/lfdemod.c +++ b/common/lfdemod.c @@ -10,8 +10,8 @@ // // NOTES: // LF Demod functions are placed here to allow the flexability to use client or -// device side. Most BUT NOT ALL of these functions are currenlty safe for -// device side use currently. (DetectST for example...) +// device side. Most BUT NOT ALL of these functions are currently safe for +// device side use. (DetectST for example...) // // There are likely many improvements to the code that could be made, please // make suggestions... diff --git a/common/protocols.h b/common/protocols.h index 57e6011f..9ba69d5c 100644 --- a/common/protocols.h +++ b/common/protocols.h @@ -200,11 +200,12 @@ NXP/Philips CUSTOM COMMANDS #define TOPAZ_WRITE_NE8 0x1B // Write-no-erase (eight bytes) -#define ISO_14443A 0 -#define ICLASS 1 -#define ISO_14443B 2 -#define TOPAZ 3 -#define PROTO_MIFARE 4 +#define ISO_14443A 0 +#define ICLASS 1 +#define ISO_14443B 2 +#define TOPAZ 3 +#define PROTO_MIFARE 4 +#define ISO_7816_4 5 //-- Picopass fuses #define FUSE_FPERS 0x80 @@ -216,6 +217,29 @@ NXP/Philips CUSTOM COMMANDS #define FUSE_FPROD0 0x02 #define FUSE_RA 0x01 +// ISO 7816-4 Basic interindustry commands. For command APDU's. +#define ISO7816_READ_BINARY 0xB0 +#define ISO7816_WRITE_BINARY 0xD0 +#define ISO7816_UPDATE_BINARY 0xD6 +#define ISO7816_ERASE_BINARY 0x0E +#define ISO7816_READ_RECORDS 0xB2 +#define ISO7816_WRITE_RECORDS 0xD2 +#define ISO7816_APPEND_RECORD 0xE2 +#define ISO7816_UPDATE_RECORD 0xDC +#define ISO7816_GET_DATA 0xCA +#define ISO7816_PUT_DATA 0xDA +#define ISO7816_SELECT_FILE 0xA4 +#define ISO7816_VERIFY 0x20 +#define ISO7816_INTERNAL_AUTHENTICATION 0x88 +#define ISO7816_EXTERNAL_AUTHENTICATION 0x82 +#define ISO7816_GET_CHALLENGE 0xB4 +#define ISO7816_MANAGE_CHANNEL 0x70 +// ISO7816-4 For response APDU's +#define ISO7816_OK 0x9000 +// 6x xx = ERROR + + + void printIclassDumpInfo(uint8_t* iclass_dump); void getMemConfig(uint8_t mem_cfg, uint8_t chip_cfg, uint8_t *max_blk, uint8_t *app_areas, uint8_t *kb); diff --git a/include/smartcard.h b/include/smartcard.h new file mode 100644 index 00000000..9bed8c9d --- /dev/null +++ b/include/smartcard.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// (c) 2018 Iceman, adapted by Marshmellow +// +// 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. +//----------------------------------------------------------------------------- +// smart card type prototyping +//----------------------------------------------------------------------------- +#ifndef __SMARTCARD_H +#define __SMARTCARD_H + +//----------------------------------------------------------------------------- +// ISO 7618 Smart Card +//----------------------------------------------------------------------------- +typedef struct { + uint8_t atr_len; + uint8_t atr[30]; +} __attribute__((__packed__)) smart_card_atr_t; + +typedef enum SMARTCARD_COMMAND { + SC_CONNECT = (1 << 0), + SC_NO_DISCONNECT = (1 << 1), + SC_RAW = (1 << 2), + SC_NO_SELECT = (1 << 3) +} smartcard_command_t; + + +#endif diff --git a/include/usb_cmd.h b/include/usb_cmd.h index 194a9d53..bdff7261 100644 --- a/include/usb_cmd.h +++ b/include/usb_cmd.h @@ -60,8 +60,17 @@ typedef struct{ #define CMD_BUFF_CLEAR 0x0105 #define CMD_READ_MEM 0x0106 #define CMD_VERSION 0x0107 -#define CMD_STATUS 0x0108 -#define CMD_PING 0x0109 +#define CMD_STATUS 0x0108 +#define CMD_PING 0x0109 + +// RDV40, Smart card operations +#define CMD_SMART_RAW 0x0140 +#define CMD_SMART_UPGRADE 0x0141 +#define CMD_SMART_UPLOAD 0x0142 +#define CMD_SMART_ATR 0x0143 +// CMD_SMART_SETBAUD is unused for now +#define CMD_SMART_SETBAUD 0x0144 +#define CMD_SMART_SETCLOCK 0x0145 // For low-frequency tags #define CMD_READ_TI_TYPE 0x0202 @@ -126,10 +135,10 @@ typedef struct{ #define CMD_READER_HITAG 0x0372 #define CMD_SIMULATE_HITAG_S 0x0368 -#define CMD_TEST_HITAGS_TRACES 0x0367 -#define CMD_READ_HITAG_S 0x0373 -#define CMD_WR_HITAG_S 0x0375 -#define CMD_EMU_HITAG_S 0x0376 +#define CMD_TEST_HITAGS_TRACES 0x0367 +#define CMD_READ_HITAG_S 0x0373 +#define CMD_WR_HITAG_S 0x0375 +#define CMD_EMU_HITAG_S 0x0376 #define CMD_SIMULATE_TAG_ISO_14443B 0x0381