From 614ab55809826056d75649c0af7b27d27d411005 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Sat, 5 Dec 2020 17:47:03 -0500 Subject: [PATCH 1/8] 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 From b0ff0ed526dfc8fb76b701d8dd91fcee4ac556c8 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Mon, 7 Dec 2020 11:18:00 -0500 Subject: [PATCH 2/8] Fix bug with manchester receive function. Using suggested algorithm from em4x70 datasheet --- armsrc/em4x70.c | 60 ++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index c52fc1738..c2f320df9 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -488,12 +488,10 @@ static bool find_EM4X70_Tag(void) { static int em4x70_receive(uint8_t *bits) { - bool bbitchange = false; uint32_t pl; int bit_pos = 0; + uint8_t edge = 0; - // Set first bit to a 1 for starting off corectly - bits[0] = 1; bool foundheader = false; @@ -529,48 +527,44 @@ static int em4x70_receive(uint8_t *bits) { // between two listen windows only pulse lengths of 1, 1.5 and 2 are possible while (true) { - bit_pos++; - pl = get_pulse_length(); + if(edge) + pl = get_pulse_length(); + else + pl = get_pulse_invert_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]; + // pulse length = 1 + bits[bit_pos++] = edge; } 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; - + // pulse length = 1.5 -> flip edge detection + if(edge) { + bits[bit_pos++] = 0; + bits[bit_pos++] = 0; + edge = 0; } else { - - bits[bit_pos] = (bits[bit_pos - 1] == 1) ? 0 : 1; - - // next time two bits have to be added - bbitchange = true; + bits[bit_pos++] = 1; + bits[bit_pos++] = 1; + edge = 1; } } 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++; + // pulse length of 2 + if(edge) { + bits[bit_pos++] = 0; + bits[bit_pos++] = 1; + } else { + bits[bit_pos++] = 1; + bits[bit_pos++] = 0; + } - } 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 + } else if ( (edge && check_pulse_length(pl, 3 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) || + (!edge && check_pulse_length(pl, 80, EM4X70_T_TAG_QUARTER_PERIOD))) { + + // LIW detected (either invert or normal) return --bit_pos; } } From 64dd8614d8f54d109b9c171c929b1751a6f3a422 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Tue, 8 Dec 2020 14:36:23 -0500 Subject: [PATCH 3/8] Remove unused cmflfem4x70 header from cmlfem410x.c --- client/src/cmdlfem410x.c | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index c6437b931..ef35e95a0 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -10,7 +10,6 @@ #include "cmdlfem410x.h" #include "cmdlfem4x50.h" -#include "cmdlfem4x70.h" #include #include From be3af8d32c83898c26947a58e31ec615fda83f5d Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Tue, 8 Dec 2020 14:38:47 -0500 Subject: [PATCH 4/8] Fix command help spacing/order for em4x70 --- client/src/cmdlfem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdlfem.c b/client/src/cmdlfem.c index 336a5a858..7ad0da3dc 100644 --- a/client/src/cmdlfem.c +++ b/client/src/cmdlfem.c @@ -27,7 +27,7 @@ static command_t CommandTable[] = { {"410x", CmdLFEM410X, AlwaysAvailable, "EM 4102 commands..."}, {"4x05", CmdLFEM4X05, AlwaysAvailable, "EM 4205 / 4305 / 4369 / 4469 commands..."}, {"4x50", CmdLFEM4X50, AlwaysAvailable, "EM 4350 / 4450 commands..."}, - {"4x70", CmdLFEM4X70, AlwaysAvailable, "EM 4170 /4070 commands..."}, + {"4x70", CmdLFEM4X70, AlwaysAvailable, "EM 4070 / 4170 commands..."}, {NULL, NULL, NULL, NULL} }; From a9dd75510b885448a6e7a6b2a2808493a45d0877 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Tue, 8 Dec 2020 16:40:24 -0500 Subject: [PATCH 5/8] Remove packed attribute on em4x70_data_t struct that only has one entry --- include/em4x70.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/em4x70.h b/include/em4x70.h index e54e09647..503b5f2e8 100644 --- a/include/em4x70.h +++ b/include/em4x70.h @@ -13,6 +13,6 @@ typedef struct { bool parity; -} PACKED em4x70_data_t; +} em4x70_data_t; #endif /* EM4X70_H__ */ From 97a27c01566700b6cdea79fcbcd47bbbcd2e244b Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Wed, 9 Dec 2020 09:57:17 -0500 Subject: [PATCH 6/8] Remove debug code --- armsrc/em4x70.c | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index c2f320df9..80f04c8e9 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -162,43 +162,6 @@ static bool get_signalproperties(void) { 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 * From e48d343c5c67b8b3384a372b916279b5f00f8260 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Wed, 9 Dec 2020 16:22:38 -0500 Subject: [PATCH 7/8] Fix issue with command parity, adding too many bits to command --- armsrc/em4x70.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 80f04c8e9..1feb448c7 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -278,12 +278,18 @@ static void em4x70_send_bit(int bit) { /** - * em4x70_send_command without parity + * em4x70_send_command */ static void em4170_send_command(uint8_t command) { int parity = 0; + int msb_bit = 0; + + // Non automotive EM4x70 based tags are 3 bits + 1 parity. + // So drop the MSB and send a parity bit instead after the command + if(command_parity) + msb_bit = 1; - for (int i = 0; i < 4; i++) { + for (int i = msb_bit; i < 4; i++) { int bit = (command >> (3 - i)) & 1; em4x70_send_bit(bit); parity ^= bit; From 76bf80a8dd743d75271ae934f8f9300856c6bde8 Mon Sep 17 00:00:00 2001 From: Christian Molson Date: Wed, 9 Dec 2020 16:22:52 -0500 Subject: [PATCH 8/8] Update em4x70 info to use cliparser --- client/src/cmdlfem4x70.c | 55 +++++++++++++--------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index 4d67b82cd..ae0b3ad22 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -11,29 +11,14 @@ #include "cmdlfem4x70.h" #include #include "cmdparser.h" // command_t +#include "cliparser.h" #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, ""); @@ -88,32 +73,28 @@ 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))) { + CLIParserContext *ctx; - case 'h': - return usage_lf_em4x70_info(); - - case 'p': - etd.parity = true; - cmdp +=1; - break; + CLIParserInit(&ctx, "lf em 4x10 info", + "Tag Information EM4x70\n" + " Tag variants include ID48 automotive transponder.\n" + " ID48 does not use command parity (default).\n" + " V4070 and EM4170 do require parity bit.", + "lf em 4x70 info\n" + "lf em 4x70 -p -> adds parity bit to commands\n" + ); - default: - PrintAndLogEx(WARNING, " Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } + void *argtable[] = { + arg_param_begin, + arg_lit0("p", "parity", "Add parity bit when sending commands"), + arg_param_end + }; - // validation - if (errors) - return usage_lf_em4x70_info(); + CLIExecWithReturn(ctx, Cmd, argtable, true); + etd.parity = arg_get_lit(ctx, 0); + CLIParserFree(ctx); clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&etd, sizeof(etd));