From 614ab55809826056d75649c0af7b27d27d411005 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Sat, 5 Dec 2020 17:47:03 -0500 Subject: [PATCH] Initial commit for em4x70 support. Initially I only have an em4x70 variant used for car transponders. Also known as the ID48. --- armsrc/Makefile | 7 + armsrc/appmain.c | 14 + armsrc/em4x70.c | 600 +++++++++++++++++++++++++++++++++++++++ armsrc/em4x70.h | 22 ++ client/CMakeLists.txt | 1 + client/Makefile | 1 + client/src/cmdlf.c | 9 + client/src/cmdlfem.c | 2 + client/src/cmdlfem410x.c | 1 + client/src/cmdlfem4x70.c | 151 ++++++++++ client/src/cmdlfem4x70.h | 25 ++ client/src/cmdparser.c | 7 + client/src/cmdparser.h | 1 + common_arm/Makefile.hal | 3 + doc/commands.md | 1 + include/em4x70.h | 18 ++ include/pm3_cmd.h | 3 + 17 files changed, 866 insertions(+) create mode 100644 armsrc/em4x70.c create mode 100644 armsrc/em4x70.h create mode 100644 client/src/cmdlfem4x70.c create mode 100644 client/src/cmdlfem4x70.h create mode 100644 include/em4x70.h diff --git a/armsrc/Makefile b/armsrc/Makefile index f3caf6e22..46ba4f027 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -69,6 +69,12 @@ else SRC_EM4x50 = endif +ifneq (,$(findstring WITH_EM4x70,$(APP_CFLAGS))) + SRC_EM4x70 = em4x70.c +else + SRC_EM4x70 = +endif + ifneq (,$(findstring WITH_LCD,$(APP_CFLAGS))) SRC_LCD = fonts.c LCD.c else @@ -106,6 +112,7 @@ THUMBSRC = start.c \ $(SRC_FPC) \ $(SRC_HITAG) \ $(SRC_EM4x50) \ + $(SRC_EM4x70) \ $(SRC_SPIFFS) \ $(SRC_ISO14443a) \ $(SRC_ISO14443b) \ diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e0178a8d7..596d50695 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -31,6 +31,7 @@ #include "hitag2.h" #include "hitagS.h" #include "em4x50.h" +#include "em4x70.h" #include "iclass.h" #include "legicrfsim.h" //#include "cryptorfsim.h" @@ -479,6 +480,12 @@ static void SendCapabilities(void) { #else capabilities.compiled_with_em4x50 = false; #endif +#ifdef WITH_EM4x70 + capabilities.compiled_with_em4x70 = true; +#else + capabilities.compiled_with_em4x70 = false; +#endif + #ifdef WITH_HFSNIFF capabilities.compiled_with_hfsniff = true; #else @@ -1120,6 +1127,13 @@ static void PacketReceived(PacketCommandNG *packet) { } #endif +#ifdef WITH_EM4x70 + case CMD_LF_EM4X70_INFO: { + em4x70_info((em4x70_data_t *)packet->data.asBytes); + break; + } +#endif + #ifdef WITH_ISO15693 case CMD_HF_ISO15693_ACQ_RAW_ADC: { AcquireRawAdcSamplesIso15693(); diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c new file mode 100644 index 000000000..c52fc1738 --- /dev/null +++ b/armsrc/em4x70.c @@ -0,0 +1,600 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins based on em4x50 +// +// 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. +//----------------------------------------------------------------------------- +// Low frequency EM4170 commands +//----------------------------------------------------------------------------- + +#include "fpgaloader.h" +#include "ticks.h" +#include "dbprint.h" +#include "lfadc.h" +#include "commonutil.h" +#include "em4x70.h" +#include "appmain.h" // tear + +static em4x70_tag_t tag = { 0 }; + +// EM4170 requires a parity bit on commands, other variants do not. +static bool command_parity = true; + +#define EM4X70_T_TAG_QUARTER_PERIOD 8 +#define EM4X70_T_TAG_HALF_PERIOD 16 +#define EM4X70_T_TAG_THREE_QUARTER_PERIOD 24 +#define EM4X70_T_TAG_FULL_PERIOD 32 +#define EM4X70_T_TAG_TWA 128 // Write Access Time +#define EM4X70_T_TAG_DIV 224 // Divergency Time +#define EM4X70_T_TAG_AUTH 4224 // Authentication Time +#define EM4X70_T_TAG_WEE 3072 // EEPROM write Time +#define EM4X70_T_TAG_TWALB 128 // Write Access Time of Lock Bits + +#define EM4X70_T_WAITING_FOR_SNGLLIW 160 // Unsure + +#define TICKS_PER_FC 12 // 1 fc = 8us, 1.5us per tick = 12 ticks +#define EM4X70_MIN_AMPLITUDE 10 // Minimum difference between a high and low signal + +#define EM4X70_TAG_TOLERANCE 10 +#define EM4X70_TAG_WORD 48 + + +/** + * These IDs are from the EM4170 datasheet + * Some versions of the chip require a fourth + * (even) parity bit, others do not + */ +#define EM4X70_COMMAND_ID 0x01 +#define EM4X70_COMMAND_UM1 0x02 +#define EM4X70_COMMAND_AUTH 0x03 +#define EM4X70_COMMAND_PIN 0x04 +#define EM4X70_COMMAND_WRITE 0x05 +#define EM4X70_COMMAND_UM2 0x07 + +static uint8_t gHigh = 0; +static uint8_t gLow = 0; + +#define IS_HIGH(sample) (sample>gLow ? true : false) +#define IS_LOW(sample) (sample timeout_ticks) + + +static uint8_t bits2byte(uint8_t *bits, int length); +static void bits2bytes(uint8_t *bits, int length, uint8_t *out); +static int em4x70_receive(uint8_t *bits); +static bool find_listen_window(bool command); + +static void init_tag(void) { + memset(tag.data, 0x00, sizeof(tag.data)/sizeof(tag.data[0])); +} + +static void EM4170_setup_read(void) { + + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC | FPGA_LF_ADC_READER_FIELD); + + // 50ms for the resonant antenna to settle. + SpinDelay(50); + + // Now set up the SSC to get the ADC samples that are now streaming at us. + FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER); + + FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); + + // Connect the A/D to the peak-detected low-frequency path. + SetAdcMuxFor(GPIO_MUXSEL_LOPKD); + + // Steal this pin from the SSP (SPI communication channel with fpga) and + // use it to control the modulation + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + + // Disable modulation at default, which means enable the field + LOW(GPIO_SSC_DOUT); + + // Start the timer + StartTicks(); + + // Watchdog hit + WDT_HIT(); +} + +static bool get_signalproperties(void) { + + // calculate signal properties (mean amplitudes) from measured data: + // 32 amplitudes (maximum values) -> mean amplitude value -> gHigh -> gLow + bool signal_found = false; + int no_periods = 32, pct = 50, noise = 140; // pct originally 75, found 50 was working better for me + uint8_t sample_ref = 127; + uint8_t sample_max_mean = 0; + uint8_t sample_max[no_periods]; + uint32_t sample_max_sum = 0; + + memset(sample_max, 0x00, sizeof(sample_max)); + + // wait until signal/noise > 1 (max. 32 periods) + for (int i = 0; i < TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD * no_periods; i++) { + + // about 2 samples per bit period + WaitTicks(TICKS_PER_FC * EM4X70_T_TAG_HALF_PERIOD); + + if (AT91C_BASE_SSC->SSC_RHR > noise) { + signal_found = true; + break; + } + + } + + if (signal_found == false) + return false; + + // calculate mean maximum value of 32 periods, each period has a length of + // 3 single "full periods" to eliminate the influence of a listen window + for (int i = 0; i < no_periods; i++) { + + uint32_t start_ticks = GetTicks(); + //AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (GetTicks() - start_ticks < TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD) { + + volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + + if (sample > sample_max[i]) + sample_max[i] = sample; + + } + + sample_max_sum += sample_max[i]; + } + + sample_max_mean = sample_max_sum / no_periods; + + // set global envelope variables + gHigh = sample_ref + pct * (sample_max_mean - sample_ref) / 100; + gLow = sample_ref - pct * (sample_max_mean - sample_ref) / 100; + + // Basic sanity check + if(gHigh - gLow < EM4X70_MIN_AMPLITUDE) { + return false; + } + + Dbprintf("%s: gHigh %d gLow: %d", __func__, gHigh, gLow); + return true; +} + + + +/** + * record_liw + * + * prints the timing from 1->0->1... for LIW_TEST_LENGTH + * + */ +/*#define LIW_TEST_LENGTH 64 +static void record_liw(void) { + + uint32_t intervals[LIW_TEST_LENGTH]; + + uint8_t sample; + + // Count duration low, then duration high. + for(int count = 0; count < LIW_TEST_LENGTH-1; count+=2) { + + uint32_t start_ticks = GetTicks(); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample)); + intervals[count] = GetTicks() - start_ticks; + + start_ticks = GetTicks(); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample)); + intervals[count+1] = GetTicks() - start_ticks; + } + + for(int count = 0; count < LIW_TEST_LENGTH-1; count+=2){ + Dbprintf("%d 0", intervals[count]/TICKS_PER_FC); + Dbprintf("%d 1", intervals[count+1]/TICKS_PER_FC); + } +}*/ + +/** + * get_pulse_length + * + * Times falling edge pulses + */ +static uint32_t get_pulse_length(void) { + + uint8_t sample; + uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + uint32_t start_ticks = GetTicks(); + timeout = start_ticks + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + timeout = (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD) + GetTicks(); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + return GetTicks() - start_ticks; +} + +/** + * get_pulse_invert_length + * + * Times rising edge pules + * TODO: convert to single function with get_pulse_length() + */ +static uint32_t get_pulse_invert_length(void) { + + uint8_t sample; + uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + uint32_t start_ticks = GetTicks(); + timeout = start_ticks + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + return GetTicks() - start_ticks; + +} + +static bool check_pulse_length(uint32_t pl, int length, int margin) { + // check if pulse length corresponds to given length + //Dbprintf("%s: pulse length %d vs %d", __func__, pl, length * TICKS_PER_FC); + return ((pl >= TICKS_PER_FC * (length - margin)) & (pl <= TICKS_PER_FC * (length + margin))); +} + +static void em4x70_send_bit(int bit) { + + // send single bit according to EM4170 application note and datasheet + + uint32_t start_ticks = GetTicks(); + + if (bit == 0) { + + // disable modulation (drop the field) for 4 cycles of carrier + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * 4); + + // enable modulation (activates the field) for remaining first + // half of bit period + HIGH(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_HALF_PERIOD); + + // disable modulation for second half of bit period + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD); + + } else { + + // bit = "1" means disable modulation for full bit period + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD); + } + +} + + +/** + * em4x70_send_command without parity + */ +static void em4170_send_command(uint8_t command) { + int parity = 0; + + for (int i = 0; i < 4; i++) { + int bit = (command >> (3 - i)) & 1; + em4x70_send_bit(bit); + parity ^= bit; + } + + if(command_parity) + em4x70_send_bit(parity); + +} + +static bool find_listen_window(bool command) { + + int cnt = 0; + while(cnt < EM4X70_T_WAITING_FOR_SNGLLIW) { + /* + 80 ( 64 + 16 ) + 80 ( 64 + 16 ) + Flip Polarity + 96 ( 64 + 32 ) + 64 ( 32 + 16 +16 )*/ + + if (check_pulse_length(get_pulse_invert_length(), 80, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_invert_length(), 80, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_length(), 96, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_length(), 64, EM4X70_TAG_TOLERANCE)) { + if(command) { + /* Here we are after the 64 duration edge. + * em4170 says we need to wait about 48 RF clock cycles. + * depends on the delay between tag and us + * + * I've found between 4-5 quarter periods (32-40) works best + */ + WaitTicks(TICKS_PER_FC * 5 * EM4X70_T_TAG_QUARTER_PERIOD); + // Send RM Command + em4x70_send_bit(0); + em4x70_send_bit(0); + } + return true; + } + } + } + } + cnt++; + } + + return false; +} + +static void bits2bytes(uint8_t *bits, int length, uint8_t *out) { + + if(length%8 != 0) { + Dbprintf("Should have a multiple of 8 bits, was sent %d", length); + } + + int num_bytes = length / 8; // We should have a multiple of 8 here + + for(int i=1; i <= num_bytes; i++) { + out[num_bytes-i] = bits2byte(bits, 8); + bits+=8; + //Dbprintf("Read: %02X", out[num_bytes-i]); + } +} + +static uint8_t bits2byte(uint8_t *bits, int length) { + + // converts separate bits into a single "byte" + uint8_t byte = 0; + for (int i = 0; i < length; i++) { + + byte |= bits[i]; + + if (i != length - 1) + byte <<= 1; + } + + return byte; +} + +/*static void print_array(uint8_t *bits, int len) { + + if(len%8 != 0) { + Dbprintf("Should have a multiple of 8 bits, was sent %d", len); + } + + int num_bytes = len / 8; // We should have a multiple of 8 here + + uint8_t bytes[8]; + + for(int i=0;i speed up "lf search" process + return find_listen_window(false); +} + +static int em4x70_receive(uint8_t *bits) { + + bool bbitchange = false; + uint32_t pl; + int bit_pos = 0; + + // Set first bit to a 1 for starting off corectly + bits[0] = 1; + + bool foundheader = false; + + // Read out the header + // 12 Manchester 1's (may miss some during settle period) + // 4 Manchester 0's + + // Skip a few leading 1's as it could be noisy + WaitTicks(TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + // wait until we get the transition from 1's to 0's which is 1.5 full windows + int pulse_count = 0; + while(pulse_count < 12){ + pl = get_pulse_invert_length(); + pulse_count++; + if(check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD, EM4X70_TAG_TOLERANCE)) { + foundheader = true; + break; + } + } + + if(!foundheader) { + Dbprintf("Failed to find read header"); + return 0; + } + + // Skip next 3 0's, header check consumes the first 0 + for(int i = 0; i < 3; i++) { + get_pulse_invert_length(); + } + + // identify remaining bits based on pulse lengths + // between two listen windows only pulse lengths of 1, 1.5 and 2 are possible + while (true) { + + bit_pos++; + pl = get_pulse_length(); + + if (check_pulse_length(pl, EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length = 1 -> keep former bit value + bits[bit_pos] = bits[bit_pos - 1]; + + } else if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length = 1.5 -> decision on bit change + + if (bbitchange) { + + // if number of pulse lengths with 1.5 periods is even -> add bit + bits[bit_pos] = (bits[bit_pos - 1] == 1) ? 1 : 0; + + // pulse length of 1.5 changes bit value + bits[bit_pos + 1] = (bits[bit_pos] == 1) ? 0 : 1; + bit_pos++; + + // next time add only one bit + bbitchange = false; + + } else { + + bits[bit_pos] = (bits[bit_pos - 1] == 1) ? 0 : 1; + + // next time two bits have to be added + bbitchange = true; + } + + } else if (check_pulse_length(pl, 2 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length of 2 means: adding 2 bits "01" + bits[bit_pos] = 0; + bits[bit_pos + 1] = 1; + bit_pos++; + + } else if (check_pulse_length(pl, 3 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + // pulse length of 3 indicates listen window -> clear last + // bit (= 0) and return + return --bit_pos; + } + } + return bit_pos; + +} + +void em4x70_info(em4x70_data_t *etd) { + + uint8_t status = 0; + + // Support tags with and without command parity bits + command_parity = etd->parity; + + init_tag(); + EM4170_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_EM4X70_Tag()) { + // Read ID, UM1 and UM2 + status = em4x70_read_id() && em4x70_read_um1() && em4x70_read_um2(); + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, sizeof(tag.data)); +} diff --git a/armsrc/em4x70.h b/armsrc/em4x70.h new file mode 100644 index 000000000..80fd977a9 --- /dev/null +++ b/armsrc/em4x70.h @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// 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. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#ifndef EM4x70_H +#define EM4x70_H + +#include "../include/em4x70.h" + +typedef struct { + uint8_t data[32]; +} em4x70_tag_t; + +void em4x70_info(em4x70_data_t *etd); + +#endif /* EM4x70_H */ diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index b630bf5de..4ea7acf36 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -252,6 +252,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfem410x.c ${PM3_ROOT}/client/src/cmdlfem4x05.c ${PM3_ROOT}/client/src/cmdlfem4x50.c + ${PM3_ROOT}/client/src/cmdlfem4x70.c ${PM3_ROOT}/client/src/cmdlffdxb.c ${PM3_ROOT}/client/src/cmdlfgallagher.c ${PM3_ROOT}/client/src/cmdlfguard.c diff --git a/client/Makefile b/client/Makefile index d577b0d25..9c624722c 100644 --- a/client/Makefile +++ b/client/Makefile @@ -493,6 +493,7 @@ SRCS = aiddesfire.c \ cmdlfem410x.c \ cmdlfem4x05.c \ cmdlfem4x50.c \ + cmdlfem4x70.c \ cmdlffdxb.c \ cmdlfguard.c \ cmdlfgallagher.c \ diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index cbecb8f87..89483bf00 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -31,6 +31,7 @@ #include "cmdlfem410x.h" // for em4x menu #include "cmdlfem4x05.h" // for em4x05 / 4x69 #include "cmdlfem4x50.h" // for em4x50 +#include "cmdlfem4x70.h" // for em4x70 #include "cmdlfhid.h" // for hid menu #include "cmdlfhitag.h" // for hitag menu #include "cmdlfidteck.h" // for idteck menu @@ -1370,6 +1371,14 @@ static bool CheckChipType(bool getDeviceData) { goto out; } + // check for em4x70 chips + if (detect_4x70_block()) { + PrintAndLogEx(SUCCESS, "Chipset detection: " _GREEN_("EM4x70")); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf em 4x70`") " commands"); + retval = true; + goto out; + } + PrintAndLogEx(NORMAL, "Couldn't identify a chipset"); out: save_restoreGB(GRAPH_RESTORE); diff --git a/client/src/cmdlfem.c b/client/src/cmdlfem.c index fbc7ac476..744a62e31 100644 --- a/client/src/cmdlfem.c +++ b/client/src/cmdlfem.c @@ -12,6 +12,7 @@ #include "cmdlfem410x.h" #include "cmdlfem4x05.h" #include "cmdlfem4x50.h" +#include "cmdlfem4x70.h" #include #include @@ -26,6 +27,7 @@ static command_t CommandTable[] = { {"410x", CmdLFEM410X, AlwaysAvailable, "EM 410x commands..."}, {"4x05", CmdLFEM4X05, AlwaysAvailable, "EM 4x05 commands..."}, {"4x50", CmdLFEM4X50, AlwaysAvailable, "EM 4x50 commands..."}, + {"4x70", CmdLFEM4X70, AlwaysAvailable, "EM 4x70 commands..."}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index ef35e95a0..c6437b931 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -10,6 +10,7 @@ #include "cmdlfem410x.h" #include "cmdlfem4x50.h" +#include "cmdlfem4x70.h" #include #include diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c new file mode 100644 index 000000000..4d67b82cd --- /dev/null +++ b/client/src/cmdlfem4x70.c @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// 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. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#include "cmdlfem4x70.h" +#include +#include "cmdparser.h" // command_t +#include "fileutils.h" +#include "comms.h" +#include "commonutil.h" +#include "em4x70.h" + + +static int CmdHelp(const char *Cmd); + + +static int usage_lf_em4x70_info(void) { + PrintAndLogEx(NORMAL, "Read all information of EM4x70. Tag must be on antenna."); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, "Usage: lf em 4x70_info [h] [v] [p ]"); + PrintAndLogEx(NORMAL, "Options:"); + PrintAndLogEx(NORMAL, " h - this help"); + PrintAndLogEx(NORMAL, " p - use even parity for commands"); + PrintAndLogEx(NORMAL, "Examples:"); + PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x70_info")); + PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x70_info p")); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + +static void print_info_result(uint8_t *data) { + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(INFO, "-------------------------------------------------------------"); + + // data section + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, _YELLOW_("EM4x70 data:")); + + for(int i=1; i <= 32; i+=2) { + PrintAndLogEx(NORMAL, "%02X %02X", data[32-i], data[32-i-1]); + } + PrintAndLogEx(NORMAL, "Tag ID: %02X %02X %02X %02X", data[7], data[6], data[5], data[4]); + PrintAndLogEx(NORMAL, "Lockbit 0: %d", (data[3] & 0x40) ? 1:0); + PrintAndLogEx(NORMAL, "Lockbit 1: %d", (data[3] & 0x80) ? 1:0); + PrintAndLogEx(NORMAL, ""); + +} + +int em4x70_info(void) { + + em4x70_data_t edata = { + .parity = false // TODO: try both? or default to true + }; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(edata)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "(em4x70) timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + print_info_result(resp.data.asBytes); + return PM3_SUCCESS; + } + + return PM3_ESOFT; +} + +//quick test for EM4x70 tag +bool detect_4x70_block(void) { + + return em4x70_info() == PM3_SUCCESS; +} + +int CmdEM4x70Info(const char *Cmd) { + + // envoke reading of a EM4x70 tag which has to be on the antenna because + // decoding is done by the device (not on client side) + + bool errors = false; + uint8_t cmdp = 0; + + em4x70_data_t etd = {0}; + + while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + switch (tolower(param_getchar(Cmd, cmdp))) { + + case 'h': + return usage_lf_em4x70_info(); + + case 'p': + etd.parity = true; + cmdp +=1; + break; + + default: + PrintAndLogEx(WARNING, " Unknown parameter '%c'", param_getchar(Cmd, cmdp)); + errors = true; + break; + } + } + + // validation + if (errors) + return usage_lf_em4x70_info(); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + print_info_result(resp.data.asBytes); + return PM3_SUCCESS; + } + + PrintAndLogEx(FAILED, "reading tag " _RED_("failed")); + return PM3_ESOFT; +} + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"info", CmdEM4x70Info, IfPm3EM4x70, "tag information EM4x70"}, + {NULL, NULL, NULL, NULL} +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdLFEM4X70(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdlfem4x70.h b/client/src/cmdlfem4x70.h new file mode 100644 index 000000000..a529678e7 --- /dev/null +++ b/client/src/cmdlfem4x70.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// 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. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#ifndef CMDLFEM4X70_H__ +#define CMDLFEM4X70_H__ + +#include "common.h" +#include "em4x50.h" + +#define TIMEOUT 2000 + +int CmdLFEM4X70(const char *Cmd); +int CmdEM4x70Info(const char *Cmd); + +int em4x70_info(void); +bool detect_4x70_block(void); + +#endif diff --git a/client/src/cmdparser.c b/client/src/cmdparser.c index 2aee4af05..137f72dc7 100644 --- a/client/src/cmdparser.c +++ b/client/src/cmdparser.c @@ -101,6 +101,13 @@ bool IfPm3EM4x50(void) { return pm3_capabilities.compiled_with_em4x50; } +bool IfPm3EM4x70(void) { + + if (!IfPm3Present()) + return false; + return pm3_capabilities.compiled_with_em4x70; +} + bool IfPm3Hfsniff(void) { if (!IfPm3Present()) return false; diff --git a/client/src/cmdparser.h b/client/src/cmdparser.h index 4e1e37a77..ff59df705 100644 --- a/client/src/cmdparser.h +++ b/client/src/cmdparser.h @@ -35,6 +35,7 @@ bool IfPm3FpcUsartFromUsb(void); bool IfPm3Lf(void); bool IfPm3Hitag(void); bool IfPm3EM4x50(void); +bool IfPm3EM4x70(void); bool IfPm3Hfsniff(void); bool IfPm3Hfplot(void); bool IfPm3Iso14443a(void); diff --git a/common_arm/Makefile.hal b/common_arm/Makefile.hal index 40e0a2c51..24d31261e 100644 --- a/common_arm/Makefile.hal +++ b/common_arm/Makefile.hal @@ -105,6 +105,9 @@ endif ifneq ($(SKIP_EM4x50),1) PLATFORM_DEFS += -DWITH_EM4x50 endif +ifneq ($(SKIP_EM4x70),1) + PLATFORM_DEFS += -DWITH_EM4x70 +endif # common HF support ifneq ($(SKIP_ISO15693),1) diff --git a/doc/commands.md b/doc/commands.md index f496ef3fe..44489d546 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -580,6 +580,7 @@ Check column "offline" for their availability. |`lf em 410x `|Y |`EM 410x commands...` |`lf em 4x05 `|Y |`EM 4x05 commands...` |`lf em 4x50 `|Y |`EM 4x50 commands...` +|`lf em 4x70 `|Y |`EM 4x70 commands...` ### lf fdxb diff --git a/include/em4x70.h b/include/em4x70.h new file mode 100644 index 000000000..e54e09647 --- /dev/null +++ b/include/em4x70.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// 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. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 structs +//----------------------------------------------------------------------------- + +#ifndef EM4X70_H__ +#define EM4X70_H__ + +typedef struct { + bool parity; +} PACKED em4x70_data_t; + +#endif /* EM4X70_H__ */ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 6acc1c1c5..d6a178a71 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -198,6 +198,7 @@ typedef struct { bool compiled_with_lf : 1; bool compiled_with_hitag : 1; bool compiled_with_em4x50 : 1; + bool compiled_with_em4x70 : 1; // hf bool compiled_with_hfsniff : 1; bool compiled_with_hfplot : 1; @@ -510,6 +511,8 @@ typedef struct { #define CMD_LF_EM4X50_WRITE_PASSWORD 0x0242 #define CMD_LF_EM4X50_READ 0x0243 #define CMD_LF_EM4X50_WIPE 0x0244 +#define CMD_LF_EM4X70_INFO 0x0250 + // Sampling configuration for LF reader/sniffer #define CMD_LF_SAMPLING_SET_CONFIG 0x021D #define CMD_LF_FSK_SIMULATE 0x021E