From fc52fbd42f576d889826f4a0c60d18fad41bc3af Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Wed, 20 Feb 2019 19:18:12 +0100 Subject: [PATCH] Add raw HF signal plotting (#786) * Add raw HF signal plotting * new fpga module hi_get_trace.v - store A/D converter output to circular buffer on FPGA * new command 'hf plot' - pull data from FPGA and display it in Graph Window --- CHANGELOG.md | 7 +- armsrc/appmain.c | 8 ++- armsrc/apps.h | 6 -- armsrc/fpgaloader.c | 22 +++++- armsrc/fpgaloader.h | 15 +++- armsrc/hfsnoop.c | 48 ++++++++++++- armsrc/hfsnoop.h | 17 +++++ armsrc/hitag2.c | 1 + armsrc/hitagS.c | 1 + armsrc/iclass.c | 1 + armsrc/iso14443a.c | 24 +++++-- armsrc/iso14443b.c | 4 +- armsrc/iso15693.c | 1 + armsrc/legicrf.c | 5 +- armsrc/legicrfsim.c | 5 +- armsrc/lfops.c | 1 + armsrc/lfsampling.c | 2 +- armsrc/mifarecmd.c | 2 +- armsrc/mifaresniff.c | 5 +- armsrc/pcf7931.c | 1 + client/cmdhf.c | 93 ++++++++++++++++++++----- client/cmdparser.h | 2 +- client/comms.c | 33 +++++++++ client/comms.h | 1 + common/fpga.h | 1 + fpga/Makefile | 2 +- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/fpga_hf.v | 34 +++++---- fpga/hi_get_trace.v | 160 +++++++++++++++++++++++++++++++++++++++++++ include/usb_cmd.h | 1 + 30 files changed, 441 insertions(+), 62 deletions(-) create mode 100644 armsrc/hfsnoop.h create mode 100644 fpga/hi_get_trace.v diff --git a/CHANGELOG.md b/CHANGELOG.md index f2c285b4..c2bf52c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac ### Fixed - AC-Mode decoding for HitagS - Wrong UID at HitagS simulation -- 'hf 15 sim' now works as expected (piwi) +- `hf 15 sim` now works as expected (piwi) ### Added - Support Standard Communication Mode in HITAG S @@ -25,8 +25,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added `hf fido` `assert` and `make` commands from fido2 protocol (authenticatorMakeCredential and authenticatorGetAssertion) (Merlok) - Added `lf paradox clone` to clone a Paradox card - Added `emv` commmands working for both contactless and smart cards (Merlok) -- Added 'hf 15 snoop' (piwi) - +- Added `hf 15 snoop` (piwi) +- Added support for standard USB Smartcard Readers (piwi) +- Added `hf plot` (piwi) ## [v3.1.0][2018-10-10] diff --git a/armsrc/appmain.c b/armsrc/appmain.c index cdc784c0..2a16f5f0 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -30,6 +30,8 @@ #include "mifareutil.h" #include "pcf7931.h" #include "i2c.h" +#include "hfsnoop.h" +#include "fpgaloader.h" #ifdef WITH_LCD #include "LCD.h" #endif @@ -1323,11 +1325,16 @@ void UsbPacketReceived(uint8_t *packet, int len) iClass_Clone(c->arg[0], c->arg[1], c->d.asBytes); break; #endif + #ifdef WITH_HFSNOOP case CMD_HF_SNIFFER: HfSnoop(c->arg[0], c->arg[1]); break; + case CMD_HF_PLOT: + HfPlot(); + break; #endif + #ifdef WITH_SMARTCARD case CMD_SMART_ATR: { SmartCardAtr(); @@ -1377,7 +1384,6 @@ void UsbPacketReceived(uint8_t *packet, int len) break; case CMD_DOWNLOAD_RAW_ADC_SAMPLES_125K: - LED_B_ON(); uint8_t *BigBuf = BigBuf_get_addr(); for(size_t i=0; iarg[1]; i += USB_CMD_DATA_SIZE) { diff --git a/armsrc/apps.h b/armsrc/apps.h index fad9e6eb..aaa128ab 100644 --- a/armsrc/apps.h +++ b/armsrc/apps.h @@ -21,7 +21,6 @@ #include "mifare.h" #include "../common/crc32.h" #include "BigBuf.h" -#include "fpgaloader.h" extern const uint8_t OddByteParity[256]; extern int rsamples; // = 0; @@ -174,13 +173,8 @@ void SimulateHitagSTag(bool tag_mem_supplied, byte_t* data); void WritePageHitagS(hitag_function htf, hitag_data* htd,int page); void check_challenges_cmd(bool file_given, byte_t* data, uint64_t tagMode); - - // cmd.h bool cmd_receive(UsbCommand* cmd); bool cmd_send(uint32_t cmd, uint32_t arg0, uint32_t arg1, uint32_t arg2, void* data, size_t len); -/// util.h -void HfSnoop(int , int); - #endif diff --git a/armsrc/fpgaloader.c b/armsrc/fpgaloader.c index 45294d60..d1d527c4 100644 --- a/armsrc/fpgaloader.c +++ b/armsrc/fpgaloader.c @@ -166,7 +166,7 @@ bool FpgaSetupSscDma(uint8_t *buf, uint16_t sample_count) AT91C_BASE_PDC_SSC->PDC_RPR = (uint32_t) buf; // transfer to this memory address AT91C_BASE_PDC_SSC->PDC_RCR = sample_count; // transfer this many samples AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t) buf; // next transfer to same memory address - AT91C_BASE_PDC_SSC->PDC_RNCR = sample_count; // ... with same number of samples AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTEN; // go! + AT91C_BASE_PDC_SSC->PDC_RNCR = sample_count; // ... with same number of samples AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTEN; // go! return true; } @@ -417,8 +417,10 @@ void FpgaDownloadAndGo(int bitstream_version) uint8_t output_buffer[OUTPUT_BUFFER_LEN] = {0x00}; // check whether or not the bitstream is already loaded - if (downloaded_bitstream == bitstream_version) + if (downloaded_bitstream == bitstream_version) { + FpgaEnableTracing(); return; + } // make sure that we have enough memory to decompress BigBuf_free(); BigBuf_Clear_ext(false); @@ -454,16 +456,30 @@ void FpgaSendCommand(uint16_t cmd, uint16_t v) while ((AT91C_BASE_SPI->SPI_SR & AT91C_SPI_TXEMPTY) == 0); // wait for the transfer to complete AT91C_BASE_SPI->SPI_TDR = AT91C_SPI_LASTXFER | cmd | v; // send the data } + //----------------------------------------------------------------------------- // Write the FPGA setup word (that determines what mode the logic is in, read // vs. clone vs. etc.). This is now a special case of FpgaSendCommand() to // avoid changing this function's occurence everywhere in the source code. //----------------------------------------------------------------------------- -void FpgaWriteConfWord(uint8_t v) +void FpgaWriteConfWord(uint16_t v) { FpgaSendCommand(FPGA_CMD_SET_CONFREG, v); } +//----------------------------------------------------------------------------- +// enable/disable FPGA internal tracing +//----------------------------------------------------------------------------- +void FpgaEnableTracing(void) +{ + FpgaSendCommand(FPGA_CMD_TRACE_ENABLE, 1); +} + +void FpgaDisableTracing(void) +{ + FpgaSendCommand(FPGA_CMD_TRACE_ENABLE, 0); +} + //----------------------------------------------------------------------------- // Set up the CMOS switches that mux the ADC: four switches, independently // closable, but should only close one at a time. Not an FPGA thing, but diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index 0600067e..006de8de 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -17,13 +17,15 @@ #include void FpgaSendCommand(uint16_t cmd, uint16_t v); -void FpgaWriteConfWord(uint8_t v); +void FpgaWriteConfWord(uint16_t v); void FpgaDownloadAndGo(int bitstream_version); void FpgaSetupSsc(uint8_t mode); void SetupSpi(int mode); bool FpgaSetupSscDma(uint8_t *buf, uint16_t sample_count); void Fpga_print_status(); int FpgaGetCurrent(); +void FpgaEnableTracing(void); +void FpgaDisableTracing(void); #define FpgaDisableSscDma(void) AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTDIS; #define FpgaEnableSscDma(void) AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTEN; void SetAdcMuxFor(uint32_t whichGpio); @@ -33,9 +35,14 @@ void SetAdcMuxFor(uint32_t whichGpio); #define FPGA_BITSTREAM_HF 2 // Definitions for the FPGA commands. +// BOTH #define FPGA_CMD_SET_CONFREG (1<<12) +// LF #define FPGA_CMD_SET_DIVISOR (2<<12) #define FPGA_CMD_SET_USER_BYTE1 (3<<12) +// HF +#define FPGA_CMD_TRACE_ENABLE (2<<12) + // Definitions for the FPGA configuration word. // LF #define FPGA_MAJOR_MODE_LF_ADC (0<<5) @@ -47,21 +54,27 @@ void SetAdcMuxFor(uint32_t whichGpio); #define FPGA_MAJOR_MODE_HF_SIMULATOR (2<<5) #define FPGA_MAJOR_MODE_HF_ISO14443A (3<<5) #define FPGA_MAJOR_MODE_HF_SNOOP (4<<5) +#define FPGA_MAJOR_MODE_HF_GET_TRACE (5<<5) // BOTH #define FPGA_MAJOR_MODE_OFF (7<<5) + // Options for LF_ADC #define FPGA_LF_ADC_READER_FIELD (1<<0) + // Options for LF_EDGE_DETECT #define FPGA_CMD_SET_EDGE_DETECT_THRESHOLD FPGA_CMD_SET_USER_BYTE1 #define FPGA_LF_EDGE_DETECT_READER_FIELD (1<<0) #define FPGA_LF_EDGE_DETECT_TOGGLE_MODE (1<<1) + // Options for the HF reader, tx to tag #define FPGA_HF_READER_TX_SHALLOW_MOD (1<<0) + // Options for the HF reader, correlating against rx from tag #define FPGA_HF_READER_RX_XCORR_848_KHZ (1<<0) #define FPGA_HF_READER_RX_XCORR_SNOOP (1<<1) #define FPGA_HF_READER_RX_XCORR_QUARTER_FREQ (1<<2) #define FPGA_HF_READER_RX_XCORR_AMPLITUDE (1<<3) + // Options for the HF simulated tag, how to modulate #define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0) #define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0) diff --git a/armsrc/hfsnoop.c b/armsrc/hfsnoop.c index e492c474..755ac0cc 100644 --- a/armsrc/hfsnoop.c +++ b/armsrc/hfsnoop.c @@ -1,10 +1,22 @@ +//----------------------------------------------------------------------------- +// piwi, 2019 +// +// 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. +//----------------------------------------------------------------------------- +// Routines to get sample data from FPGA. +//----------------------------------------------------------------------------- + +#include "hfsnoop.h" + #include "proxmark3.h" -#include "apps.h" #include "BigBuf.h" #include "util.h" +#include "apps.h" #include "usb_cdc.h" // for usb_poll_validate_length - -static void RAMFUNC optimizedSnoop(void); +#include "fpga.h" +#include "fpgaloader.h" static void RAMFUNC optimizedSnoop(void) { @@ -74,3 +86,33 @@ void HfSnoop(int samplesToSkip, int triggersToSkip) LED_D_OFF(); } +void HfPlot(void) +{ + uint8_t *buf = ToSend; + uint8_t *this_buf = buf; + + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaSetupSsc(FPGA_MAJOR_MODE_HF_GET_TRACE); + AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTDIS; // Disable DMA Transfer + AT91C_BASE_PDC_SSC->PDC_RPR = (uint32_t) this_buf; // start transfer to this memory address + AT91C_BASE_PDC_SSC->PDC_RCR = USB_CMD_DATA_SIZE; // transfer this many samples + buf[0] = (uint8_t)AT91C_BASE_SSC->SSC_RHR; // clear receive register + AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTEN; // Start DMA transfer + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_GET_TRACE); // let FPGA transfer its internal Block-RAM + + LED_B_ON(); + for(size_t i = 0; i < FPGA_TRACE_SIZE; i += USB_CMD_DATA_SIZE) { + // prepare next DMA transfer: + uint8_t *next_buf = buf + ((i + USB_CMD_DATA_SIZE) % (2 * USB_CMD_DATA_SIZE)); + AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t)next_buf; + AT91C_BASE_PDC_SSC->PDC_RNCR = USB_CMD_DATA_SIZE; + size_t len = MIN(FPGA_TRACE_SIZE - i, USB_CMD_DATA_SIZE); + while (!(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_ENDRX))) ; // wait for DMA transfer to complete + cmd_send(CMD_DOWNLOADED_RAW_ADC_SAMPLES_125K, i, len, FPGA_TRACE_SIZE, this_buf, len); + this_buf = next_buf; + } + // Trigger a finish downloading signal with an ACK frame + cmd_send(CMD_ACK, 1, 0, FPGA_TRACE_SIZE, 0, 0); + LED_B_OFF(); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); +} diff --git a/armsrc/hfsnoop.h b/armsrc/hfsnoop.h new file mode 100644 index 00000000..8c45e170 --- /dev/null +++ b/armsrc/hfsnoop.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// piwi, 2019 +// +// 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. +//----------------------------------------------------------------------------- +// Routines to get sample data from FPGA. +//----------------------------------------------------------------------------- + +#ifndef HFSNOOP_H__ +#define HFSNOOP_H__ + +void HfSnoop(int samplesToSkip, int triggersToSkip); +void HfPlot(void); + +#endif diff --git a/armsrc/hitag2.c b/armsrc/hitag2.c index 8e690a7b..0fd8d745 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -22,6 +22,7 @@ #include "hitag2.h" #include "string.h" #include "BigBuf.h" +#include "fpgaloader.h" static bool bQuiet; diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 3c247d55..8a451606 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -20,6 +20,7 @@ #include "hitag2.h" #include "string.h" #include "BigBuf.h" +#include "fpgaloader.h" #define CRC_PRESET 0xFF #define CRC_POLYNOM 0x1D diff --git a/armsrc/iclass.c b/armsrc/iclass.c index d27fc1c6..be7da703 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -51,6 +51,7 @@ #include "protocols.h" #include "optimized_cipher.h" #include "usb_cdc.h" // for usb_poll_validate_length +#include "fpgaloader.h" static int timeout = 4096; diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 50160798..2fffe837 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -25,6 +25,7 @@ #include "BigBuf.h" #include "protocols.h" #include "parity.h" +#include "fpgaloader.h" typedef struct { enum { @@ -1771,7 +1772,9 @@ int iso14443a_select_card(byte_t *uid_ptr, iso14a_card_select_t *p_hi14a_card, u if (anticollision) { // SELECT_ALL ReaderTransmit(sel_all, sizeof(sel_all), NULL); - if (!ReaderReceive(resp, resp_par)) return 0; + if (!ReaderReceive(resp, resp_par)) { + return 0; + } if (Demod.collisionPos) { // we had a collision and need to construct the UID bit by bit memset(uid_resp, 0, 4); @@ -1793,7 +1796,9 @@ int iso14443a_select_card(byte_t *uid_ptr, iso14a_card_select_t *p_hi14a_card, u } collision_answer_offset = uid_resp_bits%8; ReaderTransmitBits(sel_uid, 16 + uid_resp_bits, NULL); - if (!ReaderReceiveOffset(resp, collision_answer_offset, resp_par)) return 0; + if (!ReaderReceiveOffset(resp, collision_answer_offset, resp_par)) { + return 0; + } } // finally, add the last bits and BCC of the UID for (uint16_t i = collision_answer_offset; i < (Demod.len-1)*8; i++, uid_resp_bits++) { @@ -1827,7 +1832,9 @@ int iso14443a_select_card(byte_t *uid_ptr, iso14a_card_select_t *p_hi14a_card, u ReaderTransmit(sel_uid, sizeof(sel_uid), NULL); // Receive the SAK - if (!ReaderReceive(resp, resp_par)) return 0; + if (!ReaderReceive(resp, resp_par)) { + return 0; + } sak = resp[0]; // Test if more parts of the uid are coming @@ -1862,7 +1869,9 @@ int iso14443a_select_card(byte_t *uid_ptr, iso14a_card_select_t *p_hi14a_card, u AppendCrc14443a(rats, 2); ReaderTransmit(rats, sizeof(rats), NULL); - if (!(len = ReaderReceive(resp, resp_par))) return 0; + if (!(len = ReaderReceive(resp, resp_par))) { + return 0; + } if(p_hi14a_card) { memcpy(p_hi14a_card->ats, resp, len); @@ -2044,7 +2053,7 @@ void ReaderIso14443a(UsbCommand *c) // 1 - all is OK with ATS, 2 - without ATS cantSELECT = true; } - + FpgaDisableTracing(); LED_B_ON(); cmd_send(CMD_ACK,arg0,card->uidlen,0,buf,sizeof(iso14a_card_select_t)); LED_B_OFF(); @@ -2058,6 +2067,7 @@ void ReaderIso14443a(UsbCommand *c) if(param & ISO14A_APDU && !cantSELECT) { uint8_t res; arg0 = iso14_apdu(cmd, len, buf, &res); + FpgaDisableTracing(); LED_B_ON(); cmd_send(CMD_ACK, arg0, res, 0, buf, sizeof(buf)); LED_B_OFF(); @@ -2099,6 +2109,7 @@ void ReaderIso14443a(UsbCommand *c) } } arg0 = ReaderReceive(buf, par); + FpgaDisableTracing(); LED_B_ON(); cmd_send(CMD_ACK,arg0,0,0,buf,sizeof(buf)); @@ -2415,6 +2426,8 @@ void ReaderMifare(bool first_try) } } + FpgaDisableTracing(); + uint8_t buf[32]; memcpy(buf + 0, uid, 4); num_to_bytes(nt, 4, buf + 4); @@ -2587,6 +2600,7 @@ void RAMFUNC SniffMifare(uint8_t param) { DbpString("COMMAND FINISHED."); FpgaDisableSscDma(); + FpgaDisableTracing(); MfSniffEnd(); Dbprintf("maxDataLen=%x, Uart.state=%x, Uart.len=%x", maxDataLen, Uart.state, Uart.len); diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index 76d7a075..3ebaa539 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -10,12 +10,14 @@ // the `fake tag' modes. //----------------------------------------------------------------------------- +#include "iso14443b.h" + #include "proxmark3.h" #include "apps.h" #include "util.h" #include "string.h" - #include "iso14443crc.h" +#include "fpgaloader.h" #define RECEIVE_SAMPLES_TIMEOUT 1000 // TR0 max is 256/fs = 256/(848kHz) = 302us or 64 samples from FPGA. 1000 seems to be much too high? #define ISO14443B_DMA_BUFFER_SIZE 128 diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 9dc4bf18..13b8a174 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -60,6 +60,7 @@ #include "protocols.h" #include "cmd.h" #include "BigBuf.h" +#include "fpgaloader.h" #define arraylen(x) (sizeof(x)/sizeof((x)[0])) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index d3fd35d1..947d6cd5 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -10,15 +10,16 @@ // LEGIC RF simulation code //----------------------------------------------------------------------------- +#include "legicrf.h" + #include "proxmark3.h" #include "apps.h" #include "util.h" #include "string.h" - -#include "legicrf.h" #include "legic_prng.h" #include "legic.h" #include "crc.h" +#include "fpgaloader.h" static legic_card_select_t card;/* metadata of currently selected card */ static crc_t legic_crc; diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 1411fbea..409235af 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -10,16 +10,17 @@ // LEGIC RF simulation code //----------------------------------------------------------------------------- +#include "legicrfsim.h" + #include "proxmark3.h" #include "apps.h" #include "util.h" #include "string.h" - -#include "legicrfsim.h" #include "legic_prng.h" #include "legic.h" #include "crc.h" #include "usb_cdc.h" // for usb_poll_validate_length +#include "fpgaloader.h" static uint8_t* legic_mem; /* card memory, used for sim */ static legic_card_select_t card;/* metadata of currently selected card */ diff --git a/armsrc/lfops.c b/armsrc/lfops.c index 1816bdca..81fdd7a6 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -18,6 +18,7 @@ #include "lfsampling.h" #include "protocols.h" #include "usb_cdc.h" // for usb_poll_validate_length +#include "fpgaloader.h" /** * Function to do a modulation and then get samples. diff --git a/armsrc/lfsampling.c b/armsrc/lfsampling.c index ffbc0da8..03bccf41 100644 --- a/armsrc/lfsampling.c +++ b/armsrc/lfsampling.c @@ -12,7 +12,7 @@ #include "string.h" #include "lfsampling.h" #include "usb_cdc.h" // for usb_poll_validate_length -//#include "ticks.h" // for StartTicks +#include "fpgaloader.h" sample_config config = { 1, 8, 1, 95, 0 } ; diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index ca513cec..14ce1bcc 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -15,10 +15,10 @@ #include "mifarecmd.h" -#include "apps.h" #include "util.h" #include "parity.h" #include "crc.h" +#include "fpgaloader.h" #define HARDNESTED_AUTHENTICATION_TIMEOUT 848 // card times out 1ms after wrong authentication (according to NXP documentation) #define HARDNESTED_PRE_AUTHENTICATION_LEADTIME 400 // some (non standard) cards need a pause after select before they are ready for first authentication diff --git a/armsrc/mifaresniff.c b/armsrc/mifaresniff.c index 5391e5f9..4dbcd904 100644 --- a/armsrc/mifaresniff.c +++ b/armsrc/mifaresniff.c @@ -9,7 +9,7 @@ //----------------------------------------------------------------------------- #include "mifaresniff.h" -#include "apps.h" + #include "proxmark3.h" #include "util.h" #include "string.h" @@ -18,6 +18,9 @@ #include "crapto1/crapto1.h" #include "mifareutil.h" #include "common.h" +#include "cmd.h" +#include "BigBuf.h" +#include "fpgaloader.h" static int sniffState = SNF_INIT; diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 16b7912d..9aa0d9be 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -4,6 +4,7 @@ #include "pcf7931.h" #include "util.h" #include "string.h" +#include "fpgaloader.h" #define T0_PCF 8 //period for the pcf7931 in us #define ALLOC 16 diff --git a/client/cmdhf.c b/client/cmdhf.c index f11c5b65..73b0bc76 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -11,10 +11,12 @@ #include "cmdhf.h" +#include #include "usb_cmd.h" #include "comms.h" #include "ui.h" #include "cmdparser.h" +#include "cliparser/cliparser.h" #include "cmdhf14a.h" #include "cmdhf14b.h" #include "cmdhf15.h" @@ -27,6 +29,9 @@ #include "cmdhftopaz.h" #include "cmdhflist.h" #include "cmdhffido.h" +#include "cmddata.h" +#include "graph.h" +#include "fpga.h" static int CmdHelp(const char *Cmd); @@ -73,31 +78,83 @@ int CmdHFSnoop(const char *Cmd) return 0; } -static command_t CommandTable[] = + +// static void InterpolateShannon(int *source, size_t source_len, int *dest, size_t dest_len) +// { + // int *buf = (int*)malloc(source_len * sizeof(int)); + // memcpy(buf, source, source_len * sizeof(int)); + // for (int i = 0; i < source_len; i++) { + // buf[i] += 128; + // } + // for (int i = 0; i < dest_len; i++) { + // float value = 0.0; + // for (int j = 0; j < source_len; j++) { + // if (i * source_len == j * dest_len) { // sin(0) / 0 = 1 + // value += (float)buf[j]; + // } else { + // value += (float)buf[j] * sin(((float)i*source_len/dest_len-j)*3.1415) / (((float)i*source_len/dest_len-j)*3.1415); + // } + // } + // dest[i] = value - 128; + // } + // free(buf); +// } + + +static int CmdHFPlot(const char *Cmd) +{ + CLIParserInit("hf plot", + "Plots HF signal after RF signal path and A/D conversion.", + "This can be used after any hf command and will show the last few milliseconds of the HF signal.\n" + "Note: If the last hf command terminated because of a timeout you will most probably see nothing.\n"); + void* argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(Cmd, argtable, true); + + uint8_t buf[FPGA_TRACE_SIZE]; + + if (GetFromFpgaRAM(buf, FPGA_TRACE_SIZE)) { + for (size_t i = 0; i < FPGA_TRACE_SIZE; i++) { + GraphBuffer[i] = (int)buf[i] - 128; + } + GraphTraceLen = FPGA_TRACE_SIZE; + // InterpolateShannon(GraphBuffer, FPGA_TRACE_SIZE, GraphBuffer, FPGA_TRACE_SIZE*8/7); + // GraphTraceLen = FPGA_TRACE_SIZE*8/7; + ShowGraphWindow(); + RepaintGraphWindow(); + } + return 0; +} + + +static command_t CommandTable[] = { - {"help", CmdHelp, 1, "This help"}, - {"14a", CmdHF14A, 1, "{ ISO14443A RFIDs... }"}, - {"14b", CmdHF14B, 1, "{ ISO14443B RFIDs... }"}, - {"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"}, - {"epa", CmdHFEPA, 1, "{ German Identification Card... }"}, - {"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"}, - {"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"}, - {"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"}, - {"mfu", CmdHFMFUltra, 1, "{ MIFARE Ultralight RFIDs... }"}, - {"mfp", CmdHFMFP, 1, "{ MIFARE Plus RFIDs... }"}, - {"topaz", CmdHFTopaz, 1, "{ TOPAZ (NFC Type 1) RFIDs... }"}, - {"fido", CmdHFFido, 1, "{ FIDO and FIDO2 authenticators... }"}, - {"tune", CmdHFTune, 0, "Continuously measure HF antenna tuning"}, - {"list", CmdHFList, 1, "List protocol data in trace buffer"}, - {"search", CmdHFSearch, 1, "Search for known HF tags [preliminary]"}, + {"help", CmdHelp, 1, "This help"}, + {"14a", CmdHF14A, 0, "{ ISO14443A RFIDs... }"}, + {"14b", CmdHF14B, 0, "{ ISO14443B RFIDs... }"}, + {"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"}, + {"epa", CmdHFEPA, 0, "{ German Identification Card... }"}, + {"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"}, + {"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"}, + {"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"}, + {"mfu", CmdHFMFUltra, 1, "{ MIFARE Ultralight RFIDs... }"}, + {"mfp", CmdHFMFP, 0, "{ MIFARE Plus RFIDs... }"}, + {"topaz", CmdHFTopaz, 0, "{ TOPAZ (NFC Type 1) RFIDs... }"}, + {"fido", CmdHFFido, 0, "{ FIDO and FIDO2 authenticators... }"}, + {"tune", CmdHFTune, 0, "Continuously measure HF antenna tuning"}, + {"list", CmdHFList, 1, "List protocol data in trace buffer"}, + {"plot", CmdHFPlot, 0, "Plot signal"}, + {"search", CmdHFSearch, 0, "Search for known HF tags [preliminary]"}, {"snoop", CmdHFSnoop, 0, " Generic HF Snoop"}, - {NULL, NULL, 0, NULL} + {NULL, NULL, 0, NULL} }; int CmdHF(const char *Cmd) { CmdsParse(CommandTable, Cmd); - return 0; + return 0; } int CmdHelp(const char *Cmd) diff --git a/client/cmdparser.h b/client/cmdparser.h index 5217d04b..cd4d1625 100644 --- a/client/cmdparser.h +++ b/client/cmdparser.h @@ -9,7 +9,7 @@ //----------------------------------------------------------------------------- #ifndef CMDPARSER_H__ -#define CMDPARSER_H__ +#define CMDPARSER_H__ typedef struct command_s { diff --git a/client/comms.c b/client/comms.c index 190b9110..5af53715 100644 --- a/client/comms.c +++ b/client/comms.c @@ -301,6 +301,39 @@ bool GetFromBigBuf(uint8_t *dest, int bytes, int start_index, UsbCommand *respon } +bool GetFromFpgaRAM(uint8_t *dest, int bytes) +{ + UsbCommand c = {CMD_HF_PLOT, {0, 0, 0}}; + SendCommand(&c); + + uint64_t start_time = msclock(); + + UsbCommand response; + + int bytes_completed = 0; + bool show_warning = true; + while(true) { + if (getCommand(&response)) { + if (response.cmd == CMD_DOWNLOADED_RAW_ADC_SAMPLES_125K) { + int copy_bytes = MIN(bytes - bytes_completed, response.arg[1]); + memcpy(dest + response.arg[0], response.d.asBytes, copy_bytes); + bytes_completed += copy_bytes; + } else if (response.cmd == CMD_ACK) { + return true; + } + } + + if (msclock() - start_time > 2000 && show_warning) { + PrintAndLog("Waiting for a response from the proxmark..."); + PrintAndLog("You can cancel this operation by pressing the pm3 button"); + show_warning = false; + } + } + + return false; +} + + bool OpenProxmark(void *port, bool wait_for_port, int timeout, bool flash_mode) { char *portname = (char *)port; if (!wait_for_port) { diff --git a/client/comms.h b/client/comms.h index 68981165..65294695 100644 --- a/client/comms.h +++ b/client/comms.h @@ -35,5 +35,6 @@ bool WaitForResponseTimeoutW(uint32_t cmd, UsbCommand* response, size_t ms_timeo bool WaitForResponseTimeout(uint32_t cmd, UsbCommand* response, size_t ms_timeout); bool WaitForResponse(uint32_t cmd, UsbCommand* response); bool GetFromBigBuf(uint8_t *dest, int bytes, int start_index, UsbCommand *response, size_t ms_timeout, bool show_warning); +bool GetFromFpgaRAM(uint8_t *dest, int bytes); #endif // COMMS_H_ diff --git a/common/fpga.h b/common/fpga.h index b99a7593..65268ecf 100644 --- a/common/fpga.h +++ b/common/fpga.h @@ -10,6 +10,7 @@ #define FPGA_BITSTREAM_FIXED_HEADER_SIZE sizeof(bitparse_fixed_header) #define FPGA_INTERLEAVE_SIZE 288 #define FPGA_CONFIG_SIZE 42336L // our current fpga_[lh]f.bit files are 42175 bytes. Rounded up to next multiple of FPGA_INTERLEAVE_SIZE +#define FPGA_TRACE_SIZE 3072 static const uint8_t bitparse_fixed_header[] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01}; extern const int fpga_bitstream_num; diff --git a/fpga/Makefile b/fpga/Makefile index 2f93e741..70b0b5fe 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -5,7 +5,7 @@ clean: $(DELETE) *.bgn *.drc *.ncd *.ngd *_par.xrpt *-placed.* *-placed_pad.* *_usage.xml xst_hf.srp xst_lf.srp $(DELETE) *.map *.ngc *.xrpt *.pcf *.rbt *_auto_* *.bld *.mrp *.ngm *.unroutes *_summary.xml netlist.lst xst -fpga_hf.ngc: fpga_hf.v fpga.ucf xst_hf.scr util.v hi_simulate.v hi_read_tx.v hi_read_rx_xcorr.v hi_iso14443a.v hi_sniffer.v +fpga_hf.ngc: fpga_hf.v fpga.ucf xst_hf.scr util.v hi_simulate.v hi_read_tx.v hi_read_rx_xcorr.v hi_iso14443a.v hi_sniffer.v hi_get_trace.v $(DELETE) $@ $(XILINX_TOOLS_PREFIX)xst -ifn xst_hf.scr diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index f16fd0614fbd2614e713a29da5f47a29094953e5..2ea2c24e76d0e4b64f173d48cf67dc299283e53d 100644 GIT binary patch literal 42175 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 H^0)s7_0XSf literal 42175 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 diff --git a/fpga/fpga_hf.v b/fpga/fpga_hf.v index 5d55cb89..ef935260 100644 --- a/fpga/fpga_hf.v +++ b/fpga/fpga_hf.v @@ -18,6 +18,7 @@ `include "hi_simulate.v" `include "hi_iso14443a.v" `include "hi_sniffer.v" +`include "hi_get_trace.v" `include "util.v" module fpga_hf( @@ -40,6 +41,7 @@ module fpga_hf( reg [15:0] shift_reg; reg [7:0] conf_word; +reg trace_enable; // We switch modes between transmitting to the 13.56 MHz tag and receiving // from it, which means that we must make sure that we can do so without @@ -48,6 +50,7 @@ always @(posedge ncs) begin case(shift_reg[15:12]) 4'b0001: conf_word <= shift_reg[7:0]; // FPGA_CMD_SET_CONFREG + 4'b0010: trace_enable <= shift_reg[0]; // FPGA_CMD_TRACE_ENABLE endcase end @@ -129,7 +132,7 @@ hi_iso14443a hisn( hi_sniffer he( pck0, ck_1356meg, ck_1356megb, - he_pwr_lo, he_pwr_hi, he_pwr_oe1, he_pwr_oe2, he_pwr_oe3, he_pwr_oe4, + he_pwr_lo, he_pwr_hi, he_pwr_oe1, he_pwr_oe2, he_pwr_oe3, he_pwr_oe4, adc_d, he_adc_clk, he_ssp_frame, he_ssp_din, ssp_dout, he_ssp_clk, cross_hi, cross_lo, @@ -137,6 +140,12 @@ hi_sniffer he( hi_read_rx_xcorr_848, hi_read_rx_xcorr_snoop, hi_read_rx_xcorr_quarter ); +hi_get_trace gt( + ck_1356megb, + adc_d, trace_enable, major_mode, + gt_ssp_frame, gt_ssp_din, gt_ssp_clk +); + // Major modes: // 000 -- HF reader, transmitting to tag; modulation depth selectable @@ -144,19 +153,20 @@ hi_sniffer he( // 010 -- HF simulated tag // 011 -- HF ISO14443-A // 100 -- HF Snoop +// 101 -- HF get trace // 111 -- everything off -mux8 mux_ssp_clk (major_mode, ssp_clk, ht_ssp_clk, hrxc_ssp_clk, hs_ssp_clk, hisn_ssp_clk, he_ssp_clk, 1'b0, 1'b0, 1'b0); -mux8 mux_ssp_din (major_mode, ssp_din, ht_ssp_din, hrxc_ssp_din, hs_ssp_din, hisn_ssp_din, he_ssp_din, 1'b0, 1'b0, 1'b0); -mux8 mux_ssp_frame (major_mode, ssp_frame, ht_ssp_frame, hrxc_ssp_frame, hs_ssp_frame, hisn_ssp_frame, he_ssp_frame, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_oe1 (major_mode, pwr_oe1, ht_pwr_oe1, hrxc_pwr_oe1, hs_pwr_oe1, hisn_pwr_oe1, he_pwr_oe1, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_oe2 (major_mode, pwr_oe2, ht_pwr_oe2, hrxc_pwr_oe2, hs_pwr_oe2, hisn_pwr_oe2, he_pwr_oe2, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_oe3 (major_mode, pwr_oe3, ht_pwr_oe3, hrxc_pwr_oe3, hs_pwr_oe3, hisn_pwr_oe3, he_pwr_oe3, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_oe4 (major_mode, pwr_oe4, ht_pwr_oe4, hrxc_pwr_oe4, hs_pwr_oe4, hisn_pwr_oe4, he_pwr_oe4, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_lo (major_mode, pwr_lo, ht_pwr_lo, hrxc_pwr_lo, hs_pwr_lo, hisn_pwr_lo, he_pwr_lo, 1'b0, 1'b0, 1'b0); -mux8 mux_pwr_hi (major_mode, pwr_hi, ht_pwr_hi, hrxc_pwr_hi, hs_pwr_hi, hisn_pwr_hi, he_pwr_hi, 1'b0, 1'b0, 1'b0); -mux8 mux_adc_clk (major_mode, adc_clk, ht_adc_clk, hrxc_adc_clk, hs_adc_clk, hisn_adc_clk, he_adc_clk, 1'b0, 1'b0, 1'b0); -mux8 mux_dbg (major_mode, dbg, ht_dbg, hrxc_dbg, hs_dbg, hisn_dbg, he_dbg, 1'b0, 1'b0, 1'b0); +mux8 mux_ssp_clk (major_mode, ssp_clk, ht_ssp_clk, hrxc_ssp_clk, hs_ssp_clk, hisn_ssp_clk, he_ssp_clk, gt_ssp_clk, 1'b0, 1'b0); +mux8 mux_ssp_din (major_mode, ssp_din, ht_ssp_din, hrxc_ssp_din, hs_ssp_din, hisn_ssp_din, he_ssp_din, gt_ssp_din, 1'b0, 1'b0); +mux8 mux_ssp_frame (major_mode, ssp_frame, ht_ssp_frame, hrxc_ssp_frame, hs_ssp_frame, hisn_ssp_frame, he_ssp_frame, gt_ssp_frame, 1'b0, 1'b0); +mux8 mux_pwr_oe1 (major_mode, pwr_oe1, ht_pwr_oe1, hrxc_pwr_oe1, hs_pwr_oe1, hisn_pwr_oe1, he_pwr_oe1, 1'b0, 1'b0, 1'b0); +mux8 mux_pwr_oe2 (major_mode, pwr_oe2, ht_pwr_oe2, hrxc_pwr_oe2, hs_pwr_oe2, hisn_pwr_oe2, he_pwr_oe2, 1'b0, 1'b0, 1'b0); +mux8 mux_pwr_oe3 (major_mode, pwr_oe3, ht_pwr_oe3, hrxc_pwr_oe3, hs_pwr_oe3, hisn_pwr_oe3, he_pwr_oe3, 1'b0, 1'b0, 1'b0); +mux8 mux_pwr_oe4 (major_mode, pwr_oe4, ht_pwr_oe4, hrxc_pwr_oe4, hs_pwr_oe4, hisn_pwr_oe4, he_pwr_oe4, 1'b0, 1'b0, 1'b0); +mux8 mux_pwr_lo (major_mode, pwr_lo, ht_pwr_lo, hrxc_pwr_lo, hs_pwr_lo, hisn_pwr_lo, he_pwr_lo, 1'b0, 1'b0, 1'b0); +mux8 mux_pwr_hi (major_mode, pwr_hi, ht_pwr_hi, hrxc_pwr_hi, hs_pwr_hi, hisn_pwr_hi, he_pwr_hi, 1'b0, 1'b0, 1'b0); +mux8 mux_adc_clk (major_mode, adc_clk, ht_adc_clk, hrxc_adc_clk, hs_adc_clk, hisn_adc_clk, he_adc_clk, 1'b0, 1'b0, 1'b0); +mux8 mux_dbg (major_mode, dbg, ht_dbg, hrxc_dbg, hs_dbg, hisn_dbg, he_dbg, 1'b0, 1'b0, 1'b0); // In all modes, let the ADC's outputs be enabled. assign adc_noe = 1'b0; diff --git a/fpga/hi_get_trace.v b/fpga/hi_get_trace.v new file mode 100644 index 00000000..7acecf80 --- /dev/null +++ b/fpga/hi_get_trace.v @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// +// piwi, Feb 2019 +//----------------------------------------------------------------------------- + +module hi_get_trace( + ck_1356megb, + adc_d, trace_enable, major_mode, + ssp_frame, ssp_din, ssp_clk +); + input ck_1356megb; + input [7:0] adc_d; + input trace_enable; + input [2:0] major_mode; + output ssp_frame, ssp_din, ssp_clk; + +// constants for some major_modes: +`define OFF 3'b111 +`define GET_TRACE 3'b101 + + +// clock divider +reg [6:0] clock_cnt; +always @(negedge ck_1356megb) +begin + clock_cnt <= clock_cnt + 1; +end + +// sample at 13,56MHz / 8. The highest signal frequency (subcarrier) is 848,5kHz, i.e. in this case we oversample by a factor of 2 +reg [2:0] sample_clock; +always @(negedge ck_1356megb) +begin + if (sample_clock == 3'd3) + sample_clock <= 3'd0; + else + sample_clock <= sample_clock + 1; +end + + +reg [11:0] addr; +reg [11:0] start_addr; +reg [2:0] previous_major_mode; +reg write_enable1; +reg write_enable2; +always @(negedge ck_1356megb) +begin + previous_major_mode <= major_mode; + if (major_mode == `GET_TRACE) + begin + write_enable1 <= 1'b0; + write_enable2 <= 1'b0; + if (previous_major_mode != `GET_TRACE) // just switched into GET_TRACE mode + addr <= start_addr; + if (clock_cnt == 7'd0) + begin + if (addr == 12'd3071) + addr <= 12'd0; + else + addr <= addr + 1; + end + end + else if (major_mode != `OFF) + begin + if (trace_enable) + begin + if (addr[11] == 1'b0) + begin + write_enable1 <= 1'b1; + write_enable2 <= 1'b0; + end + else + begin + write_enable1 <= 1'b0; + write_enable2 <= 1'b1; + end + if (sample_clock == 3'b000) + begin + if (addr == 12'd3071) + begin + addr <= 12'd0; + write_enable1 <= 1'b1; + write_enable2 <= 1'b0; + end + else + addr <= addr + 1; + end + end + else + begin + write_enable1 <= 1'b0; + write_enable2 <= 1'b0; + start_addr <= addr; + end + end + else // major_mode == `OFF + begin + write_enable1 <= 1'b0; + write_enable2 <= 1'b0; + if (previous_major_mode != `OFF && previous_major_mode != `GET_TRACE) // just switched off + start_addr <= addr; + end +end + + +// (2+1)k RAM +reg [7:0] D_out1, D_out2; +reg [7:0] ram1 [2047:0]; +reg [7:0] ram2 [1023:0]; + +always @(negedge ck_1356megb) +begin + if (write_enable1) + begin + ram1[addr[10:0]] <= adc_d; + D_out1 <= adc_d; + end + else + D_out1 <= ram1[addr[10:0]]; + if (write_enable2) + begin + ram2[addr[9:0]] <= adc_d; + D_out2 <= adc_d; + end + else + D_out2 <= ram2[addr[9:0]]; +end + + +// SSC communication to ARM +reg ssp_clk; +reg ssp_frame; +reg [7:0] shift_out; + +always @(negedge ck_1356megb) +begin + if(clock_cnt[3:0] == 4'd0) // update shift register every 16 clock cycles + begin + if(clock_cnt[6:4] == 3'd0) // either load new value + begin + if (addr[11] == 1'b0) + shift_out <= D_out1; + else + shift_out <= D_out2; + end + else // or shift left + shift_out[7:1] <= shift_out[6:0]; + end + + ssp_clk <= ~clock_cnt[3]; // ssp_clk frequency = 13,56MHz / 16 = 847,5 kHz + + if(clock_cnt[6:4] == 3'b000) // set ssp_frame for 0...31 + ssp_frame <= 1'b1; + else + ssp_frame <= 1'b0; + +end + +assign ssp_din = shift_out[7]; + +endmodule diff --git a/include/usb_cmd.h b/include/usb_cmd.h index 4c27ac85..ef282256 100644 --- a/include/usb_cmd.h +++ b/include/usb_cmd.h @@ -220,6 +220,7 @@ typedef struct{ #define CMD_MIFARE_DESFIRE 0x072e #define CMD_HF_SNIFFER 0x0800 +#define CMD_HF_PLOT 0x0801 #define CMD_UNKNOWN 0xFFFF -- 2.39.2