diff --git a/CHANGELOG.md b/CHANGELOG.md index de0373163..ef36f69b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - EM4x50: changed cli parameter from w (word) to d (data) (@tharexde) + - EM4x50: new function 4x50 login: authenticate against tag (@tharexde) + - EM4x50: new function 4x50 brute: guess password within a given password range (@tharexde) + - EM4x50: new function 4x50 chk: try passwords from dictionary (without option -> T55xx default dictionary or -f user dictionary) (@tharexde) + - EM4x50: new function 4x50 reader: read data from tag (configured data -> standard read mode), incl. option -@ (@tharexde) + - EM4x50: new function 4x50 sim: simulate dump from file or emulator/flash (@tharexde) + - EM4x50: new function 4x50 restore: restore dump file (bin, eml, json) onto tag (@tharexde) + - EM4x50: new function 4x50 esave: dump em4x50 content in emulator memory to file (bin + eml + json) (@tharexde) + - EM4x50: new function 4x50 eload: upload em4x50 file content (bin, eml, json) to emulator memory (@tharexde) + - EM4x50: added LED signals (@tharexde) + - EM4x50: added json format for 4x50 dump (@tharexde) + - EM4x50: relocated write requests in function 4x50 wipe from device to client (@tharexde) + - EM4x50: renamed 4x50_write_password to 4x50 writepwd (@tharexde) + - EM4x50: all hex input parameters now have to be given in lsb format (output is still msb + lsb) (@tharexde) + - EM4x50: changed cli parameter from a (address) to b (block) (@tharexde) + - EM4x50: switched to cliparser for all functions (@tharexde) + - EM4x50: stabilized and accelerated tag detection (@tharexde) + - EM4x50: removed global tag structure on device side (@tharexde) - Fix `hf 15 sim` - Added basic response to GET_SYSTEM_INFO and READBLOCK requests in order to fix iso15693 tag sim - Added `mf mfu sim t 7 n ` - MFU emulation now supports automatic exit after blocks read. (@cyberpunk-re) - Added T55xx Guide to assist in learning how to use the T55xx chip (@mwalker33) diff --git a/armsrc/Standalone/lf_tharexde.c b/armsrc/Standalone/lf_tharexde.c new file mode 100644 index 000000000..887100855 --- /dev/null +++ b/armsrc/Standalone/lf_tharexde.c @@ -0,0 +1,359 @@ +//----------------------------------------------------------------------------- +// Tharexde, 2020 +// +// 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. +//----------------------------------------------------------------------------- +// main code for EM4x50 simulator and collector aka THAREXDE +//----------------------------------------------------------------------------- +#include +#include "standalone.h" +#include "proxmark3_arm.h" +#include "appmain.h" +#include "BigBuf.h" +#include "fpgaloader.h" +#include "util.h" +#include "dbprint.h" +#include "spiffs.h" +#include "../em4x50.h" + +/* + * `lf_tharexde` simulates hardcoded words/blocks, reads words of standard read + * mode of EM4x50 tags and stores them in internal flash. + * It requires RDV4 hardware (for flash and battery). + * + * On entering stand-alone mode, this module will start reading/record EM4x50 data. + * Every found / collected data will be written/appended to the logfile in flash + * as a text string. + * + * LEDs: + * - LED A: simulating + * - LED B: reading / record + * - LED C: writing to flash + * - LED D: unmounting/sync'ing flash (normally < 100ms) + * + * To retrieve log file from flash: + * + * 1. mem spiffs dump o lf_em4x50collect.log f lf_em4x50collect.log + * Copies log file from flash to your client. + * + * 2. exit the Proxmark3 client + * + * 3. more lf_tharexdecollect.log + * + * This module emits debug strings during normal operation -- so try it out in + * the lab connected to PM3 client before taking it into the field. + * + * To delete the log file from flash: + * + * 1. mem spiffs remove lf_tharexdecollect.log + */ + +#define STATE_SIM 0 +#define STATE_READ 1 +#define STATE_BRUTE 2 +#define EM4X50_TAG_WORD 45 +#define EM4X50_PWD_SPEED 27 +#define LF_EM4X50SIMULATE_INPUTFILE "lf_em4x50simulate.eml" +#define LF_EM4X50COLLECT_LOGFILE "lf_em4x50collect.log" +#define LF_EM4X50BRUTE_INPUTFILE "lf_em4x50brute.eml" +#define LF_EM4X50BRUTE_LOGFILE "lf_em4x50brute.log" + +bool input_exists; +bool log_exists; + +static void LoadDataInstructions(const char *inputfile) { + Dbprintf(""); + Dbprintf("To load datafile into flash and display it:"); + Dbprintf(_YELLOW_("1.") " edit inputfile %s", inputfile); + Dbprintf(_YELLOW_("2.") " start proxmark3 client"); + Dbprintf(_YELLOW_("3.") " mem spiffs load f %s o %s", inputfile, inputfile); + Dbprintf(_YELLOW_("4.") " start standalone mode"); +} + +static void DownloadLogInstructions(const char *logfile) { + Dbprintf(""); + Dbprintf("To get the logfile from flash and display it:"); + Dbprintf(_YELLOW_("1.") " mem spiffs dump o %s f %s", logfile, logfile); + Dbprintf(_YELLOW_("2.") " exit proxmark3 client"); + Dbprintf(_YELLOW_("3.") " cat %s", logfile); +} + +static int get_input_data_from_file(uint32_t *words, char *inputfile) { + + size_t now = 0; + + if (exists_in_spiffs(inputfile)) { + + uint32_t size = size_in_spiffs(inputfile); + uint8_t *mem = BigBuf_malloc(size); + + Dbprintf(_YELLOW_("found input file %s"), inputfile); + + rdv40_spiffs_read_as_filetype(inputfile, mem, size, RDV40_SPIFFS_SAFETY_SAFE); + + now = size / 9; + for (int i = 0; i < now; i++) + for (int j = 0; j < 4; j++) + words[i] |= (hex2int(mem[2 * j + 9 * i]) << 4 | hex2int(mem[2 * j + 1 + 9 * i])) << ((3 - j) * 8); + + Dbprintf(_YELLOW_("read data from input file")); + } + + BigBuf_free(); + + return (now > 0) ? now : 0; +} + +static void append(const char *filename, uint8_t *entry, size_t entry_len) { + + LED_D_ON(); + if (log_exists == false) { + rdv40_spiffs_write(filename, entry, entry_len, RDV40_SPIFFS_SAFETY_SAFE); + log_exists = true; + } else { + rdv40_spiffs_append(filename, entry, entry_len, RDV40_SPIFFS_SAFETY_SAFE); + } + LED_D_OFF(); +} + +void ModInfo(void) { + DbpString(_YELLOW_(" LF EM4x50 sim/collector/bruteforce mode") " - a.k.a tharexde"); +} + +void RunMod(void) { + + bool state_change = true;//, password_found = false; + int pwd_found = false; + uint8_t state = STATE_SIM; + // declarations for simulating + uint32_t words[33] = {0x0}; + uint32_t pwd = 0x0; + uint32_t passwords[2] = {0x0}; + size_t now = 0; + // declarations for reading + int no_words = 0; + //uint32_t words[EM4X50_TAG_WORD]; + uint8_t entry[81]; + + rdv40_spiffs_lazy_mount(); + + StandAloneMode(); + Dbprintf(_YELLOW_("Standalone mode THAREXDE started")); + + for (;;) { + + WDT_HIT(); + if (data_available()) break; + + // press button - toggle between SIM, READ and BRUTE + // hold button - exit + int button_pressed = BUTTON_CLICKED(1000); + if (button_pressed == BUTTON_SINGLE_CLICK) { + + SpinUp(100); + + switch (state) { + + case STATE_SIM: + state = STATE_READ; + break; + case STATE_READ: + state = STATE_BRUTE; + break; + case STATE_BRUTE: + state = STATE_SIM; + break; + default: + break; + } + + state_change = true; + + } else if (button_pressed == BUTTON_HOLD) { + + SpinDown(100); + break; + } + + if (state == STATE_SIM) { + + if (state_change) { + + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT); + FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); + + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT | GPIO_SSC_CLK; + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_ODR = GPIO_SSC_CLK; + + LEDsoff(); + LED_A_ON(); + Dbprintf(""); + Dbprintf(_YELLOW_("switched to EM4x50 simulating mode")); + + now = get_input_data_from_file(words, LF_EM4X50SIMULATE_INPUTFILE); + if (now > 0) { + Dbprintf(_YELLOW_("simulating %i blocks"), now); + for (int i = 0; i < now; i++) + Dbprintf("%2i -> %lx", i + 1, words[i]); + + } else { + Dbprintf(_RED_("error in input data")); + } + + state_change = false; + } + + em4x50_sim_send_listen_window(); + for (int i = 0; i < now; i++) { + em4x50_sim_send_listen_window(); + em4x50_sim_send_word(words[i]); + } + + } else if (state == STATE_READ) { + + if (state_change) { + + LEDsoff(); + LED_B_ON(); + Dbprintf(""); + Dbprintf(_YELLOW_("switched to EM4x50 reading mode")); + + memset(entry, 0, sizeof(entry)); + memset(words, 0, sizeof(words)); + + log_exists = exists_in_spiffs(LF_EM4X50COLLECT_LOGFILE); + + state_change = false; + } + + no_words = em4x50_standalone_read(words); + + if (no_words > 0) { + + memset(entry, 0, sizeof(entry)); + + sprintf((char *)entry, "found new EM4x50 tag:"); + Dbprintf("%s", entry); + strcat((char *)entry, "\n"); + append(LF_EM4X50COLLECT_LOGFILE, entry, strlen((char *)entry)); + + for (int i = 0; i < no_words; i++) { + + sprintf((char *)entry, " %2i -> 0x%08"PRIx32"", i + 1, words[i]); + Dbprintf("%s", entry); + strcat((char *)entry, "\n"); + append(LF_EM4X50COLLECT_LOGFILE, entry, strlen((char *)entry)); + } + } + + } else if (state == STATE_BRUTE) { + + if (state_change) { + + LEDsoff(); + LED_C_ON(); + Dbprintf(""); + Dbprintf(_YELLOW_("switched to EM4x50 brute force mode")); + + log_exists = exists_in_spiffs(LF_EM4X50BRUTE_LOGFILE); + now = get_input_data_from_file(passwords, LF_EM4X50BRUTE_INPUTFILE); + + if (now == 2) { + + // print some information + int no_iter = passwords[1] - passwords[0] + 1; + int dur_s = no_iter / EM4X50_PWD_SPEED; + int dur_h = dur_s / 3600; + int dur_m = (dur_s - dur_h * 3600) / 60; + dur_s -= dur_h * 3600 + dur_m * 60; + + //iterprint = no_iter/10; + + Dbprintf(_YELLOW_("trying %i passwords in range [0x%08x, 0x%08x]"), + no_iter, passwords[0], passwords[1]); + Dbprintf(_YELLOW_("estimated duration: %ih%im%is"), + dur_h, dur_m, dur_s); + + } else { + Dbprintf(_RED_("error in input data")); + break; + } + + state_change = false; + } + + pwd_found = em4x50_standalone_brute(passwords[0], passwords[1], &pwd); + + if (pwd_found == PM3_ETIMEOUT) { + + // timeout -> no EM4x50 tag on reader? + Dbprintf(_YELLOW_("timeout - no EM4x50 tag detected")); + + } else if (pwd_found == true) { + + // password found -> write to logfile + sprintf((char *)entry, "password found: 0x%08"PRIx32, pwd); + Dbprintf(_YELLOW_("%s"), entry); + strcat((char *)entry, "\n"); + append(LF_EM4X50BRUTE_LOGFILE, entry, strlen((char *)entry)); + + break; + + } else { + + if (pwd == passwords[1] + 1) { + + // finished without success -> write to logfile + sprintf((char *)entry, "no password found"); + Dbprintf(_YELLOW_("%s"), entry); + strcat((char *)entry, "\n"); + append(LF_EM4X50BRUTE_LOGFILE, entry, strlen((char *)entry)); + + + } else { + + // stopped -> write to logfile + sprintf((char *)entry, "stopped search - last password: 0x%08"PRIx32, pwd); + Dbprintf(_YELLOW_("%s"), entry); + strcat((char *)entry, "\n"); + append(LF_EM4X50BRUTE_LOGFILE, entry, strlen((char *)entry)); + + // replace start password by last tested password in + // inputfile (spiffs) so that brute forcing process will + // be continued when envoking brute force mode again + sprintf((char *)entry, "%08"PRIx32"\n%08"PRIx32"\n", pwd, passwords[1]); + rdv40_spiffs_write(LF_EM4X50BRUTE_INPUTFILE, + entry, + strlen((char *)entry), + RDV40_SPIFFS_SAFETY_SAFE); + + } + + break; + } + } + } + + if (state == STATE_READ) { + DownloadLogInstructions(LF_EM4X50COLLECT_LOGFILE); + } else if (state == STATE_BRUTE) { + LoadDataInstructions(LF_EM4X50BRUTE_INPUTFILE); + DownloadLogInstructions(LF_EM4X50BRUTE_LOGFILE); + } else { + LoadDataInstructions(LF_EM4X50SIMULATE_INPUTFILE); + } + + LED_D_ON(); + rdv40_spiffs_lazy_unmount(); + LED_D_OFF(); + + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LEDsoff(); + Dbprintf(""); + Dbprintf(_YELLOW_("[=] Standalone mode THAREXDE stopped")); + +} diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e0178a8d7..d4893359a 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1106,16 +1106,54 @@ static void PacketReceived(PacketCommandNG *packet) { em4x50_write((em4x50_data_t *)packet->data.asBytes); break; } - case CMD_LF_EM4X50_WRITE_PASSWORD: { - em4x50_write_password((em4x50_data_t *)packet->data.asBytes); + case CMD_LF_EM4X50_WRITEPWD: { + em4x50_writepwd((em4x50_data_t *)packet->data.asBytes); break; } case CMD_LF_EM4X50_READ: { em4x50_read((em4x50_data_t *)packet->data.asBytes); break; } - case CMD_LF_EM4X50_WIPE: { - em4x50_wipe((em4x50_data_t *)packet->data.asBytes); + case CMD_LF_EM4X50_BRUTE: { + em4x50_brute((em4x50_data_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X50_LOGIN: { + em4x50_login((uint32_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X50_SIM: { + //----------------------------------------------------------------------------- + // Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_LF) here although FPGA is not + // involved in dealing with emulator memory. But if it is called later, it might + // destroy the Emulator Memory. + //----------------------------------------------------------------------------- + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + em4x50_sim((uint8_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X50_READER: { + em4x50_reader(); + break; + } + case CMD_LF_EM4X50_ESET: { + //----------------------------------------------------------------------------- + // Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_LF) here although FPGA is not + // involved in dealing with emulator memory. But if it is called later, it might + // destroy the Emulator Memory. + //----------------------------------------------------------------------------- + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + emlSet(packet->data.asBytes, packet->oldarg[0], packet->oldarg[1]); + break; + } + case CMD_LF_EM4X50_CHK: { + //----------------------------------------------------------------------------- + // Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_LF) here although FPGA is not + // involved in dealing with emulator memory. But if it is called later, it might + // destroy the Emulator Memory. + //----------------------------------------------------------------------------- + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + em4x50_chk((uint8_t *)packet->data.asBytes); break; } #endif diff --git a/armsrc/em4x50.c b/armsrc/em4x50.c index 27f5a08d9..620083a32 100644 --- a/armsrc/em4x50.c +++ b/armsrc/em4x50.c @@ -12,54 +12,13 @@ #include "ticks.h" #include "dbprint.h" #include "lfadc.h" +#include "lfdemod.h" #include "commonutil.h" #include "em4x50.h" +#include "BigBuf.h" +#include "spiffs.h" #include "appmain.h" // tear -// 4 data bytes -// + byte with row parities -// + column parity byte -// + byte with stop bit - -static em4x50_tag_t tag = { - .sectors = { - [0] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // password - [1] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // protection word - [2] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // control word - [3] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [4] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [5] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [7] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [9] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [10] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [11] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [13] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [14] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [15] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [17] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [18] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [19] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [20] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [21] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [22] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [23] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [25] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [26] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [27] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [28] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [29] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [30] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [31] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user - [32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // device serial number - [33] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // device identification - }, -}; - // Sam7s has several timers, we will use the source TIMER_CLOCK1 (aka AT91C_TC_CLKS_TIMER_DIV1_CLOCK) // TIMER_CLOCK1 = MCK/2, MCK is running at 48 MHz, Timer is running at 48/2 = 24 MHz // EM4x50 units (T0) have duration of 8 microseconds (us), which is 1/125000 per second (carrier) @@ -75,111 +34,85 @@ static em4x50_tag_t tag = { #define EM4X50_T_TAG_FULL_PERIOD 64 #define EM4X50_T_TAG_TPP 64 #define EM4X50_T_TAG_TWA 64 -#define EM4X50_T_WAITING_FOR_SNGLLIW 50 +#define EM4X50_T_TAG_WAITING_FOR_SIGNAL 75 #define EM4X50_T_WAITING_FOR_DBLLIW 1550 +#define EM4X50_T_WAITING_FOR_SNGLLIW 140 // this value seems to be + // critical; + // if it's too low + // (e.g. < 120) some cards + // are no longer readable + // although they're ok #define EM4X50_TAG_TOLERANCE 8 #define EM4X50_TAG_WORD 45 -#define EM4X50_BIT_0 0 -#define EM4X50_BIT_1 1 -#define EM4X50_BIT_OTHER 2 - #define EM4X50_COMMAND_LOGIN 0x01 #define EM4X50_COMMAND_RESET 0x80 #define EM4X50_COMMAND_WRITE 0x12 #define EM4X50_COMMAND_WRITE_PASSWORD 0x11 #define EM4X50_COMMAND_SELECTIVE_READ 0x0A -#define EM4X50_COMMAND_TIMEOUT 5000 -#define FPGA_TIMER_0 0 +int gHigh = 190; +int gLow = 60; -int gHigh = 0; -int gLow = 0; +static void wait_timer(uint32_t period) { -// auxiliary functions + // do nothing for using timer0 -static void init_tag(void) { - - // iceman: memset(tag.sectors, 0x00, sizeof)); - - // initialize global tag structure - for (int i = 0; i < 34; i++) - for (int j = 0; j < 7; j++) - tag.sectors[i][j] = 0x00; + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (AT91C_BASE_TC0->TC_CV < period); } -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 msb2lsb_word(uint8_t *word) { - - // reorders given according to EM4x50 datasheet (msb -> lsb) - - uint8_t buff[4]; - buff[0] = reflect8(word[3]); - buff[1] = reflect8(word[2]); - buff[2] = reflect8(word[1]); - buff[3] = reflect8(word[0]); - - word[0] = buff[0]; - word[1] = buff[1]; - word[2] = buff[2]; - word[3] = buff[3]; -} - -static void save_word(int pos, uint8_t bits[EM4X50_TAG_WORD]) { - - // split "raw" word into data, row and column parity bits and stop bit and - // save them in global tag structure - uint8_t row_parity[4]; - uint8_t col_parity[8]; - - // data and row parities +static bool extract_parities(uint64_t word, uint32_t *data) { + + // extract and check parities + // return result of parity check and extracted plain data + + uint8_t row_parities = 0x0, col_parities = 0x0; + uint8_t row_parities_calculated = 0x0, col_parities_calculated = 0x0; + + *data = 0x0; + + // extract plain data (32 bits) from raw word (45 bits) for (int i = 0; i < 4; i++) { - tag.sectors[pos][i] = bits2byte(&bits[9 * i], 8); - row_parity[i] = bits[9 * i + 8]; + *data <<= 8; + *data |= (word >> ((4 - i) * 9 + 1)) & 0xFF; + } + + // extract row parities (4 bits + stop bit) from raw word (45 bits) + for (int i = 0; i < 5; i++) { + row_parities <<= 1; + row_parities |= (word >> ((4 - i) * 9)) & 0x1; } - tag.sectors[pos][4] = bits2byte(row_parity, 4); + // extract col_parities (8 bits, no stop bit) from raw word (45 bits) + col_parities = (word >> 1) & 0xFF; - // column parities - for (int i = 0; i < 8; i++) - col_parity[i] = bits[36 + i]; - - tag.sectors[pos][5] = bits2byte(col_parity, 8); - - // stop bit - tag.sectors[pos][6] = bits[44]; -} - -static void wait_timer(int timer, uint32_t period) { - - // do nothing for using timer - - if (timer == FPGA_TIMER_0) { - - AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; - while (AT91C_BASE_TC0->TC_CV < period); - - } else { - - AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG; - while (AT91C_BASE_TC1->TC_CV < period); + // check extracted parities against extracted data + // calculate row parities from data + for (int i = 0; i < 4; i++) { + row_parities_calculated <<= 1; + for (int j = 0; j < 8; j++) { + row_parities_calculated ^= (*data >> ((3 - i) * 8 + (7 - j))) & 0x1; + } } + + // add stop bit (always zero) + row_parities_calculated <<= 1; + + // calculate column parities from data + for (int i = 0; i < 8; i++) { + col_parities_calculated <<= 1; + for (int j = 0; j < 4; j++) { + col_parities_calculated ^= (*data >> ((3 - j) * 8 + (7 - i))) & 0x1; + } + } + + if ((row_parities == row_parities_calculated) && (col_parities == col_parities_calculated)) + return true; + + return false; } static void em4x50_setup_read(void) { @@ -208,23 +141,19 @@ static void em4x50_setup_read(void) { // Enable Peripheral Clock for // TIMER_CLOCK0, used to measure exact timing before answering - // TIMER_CLOCK1, used to capture edges of the tag frames - AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1); + AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0);// | (1 << AT91C_ID_TC1); AT91C_BASE_PIOA->PIO_BSR = GPIO_SSC_FRAME; // Disable timer during configuration AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // TC0: Capture mode, default timer source = MCK/2 (TIMER_CLOCK1), no triggers AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV1_CLOCK; // TC1: Capture mode, default timer source = MCK/2 (TIMER_CLOCK1), no triggers - AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_TIMER_DIV1_CLOCK; // Enable and reset counters AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // synchronized startup procedure while (AT91C_BASE_TC0->TC_CV > 0) {}; // wait until TC1 returned to zero @@ -233,7 +162,15 @@ static void em4x50_setup_read(void) { WDT_HIT(); } -// functions for "reader" use case +static void em4x50_setup_sim(void) { + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT); + FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); + + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT | GPIO_SSC_CLK; + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_ODR = GPIO_SSC_CLK; +} static bool get_signalproperties(void) { @@ -246,22 +183,29 @@ static bool get_signalproperties(void) { uint8_t sample_max_mean = 0; uint8_t sample_max[no_periods]; uint32_t sample_max_sum = 0; - memcpy(sample_max, 0x00, sizeof(sample_max)); + memset(sample_max, 0x00, sizeof(sample_max)); + + LED_A_ON(); // wait until signal/noise > 1 (max. 32 periods) - for (int i = 0; i < T0 * no_periods; i++) { + for (int i = 0; i < EM4X50_T_TAG_WAITING_FOR_SIGNAL; i++) { + + if (BUTTON_PRESS()) return false; // about 2 samples per bit period - wait_timer(0, T0 * EM4X50_T_TAG_HALF_PERIOD); - - if (AT91C_BASE_SSC->SSC_RHR > noise) { + wait_timer(T0 * EM4X50_T_TAG_HALF_PERIOD); + + // ignore first samples + if ((i > SIGNAL_IGNORE_FIRST_SAMPLES) && (AT91C_BASE_SSC->SSC_RHR > noise)) { signal_found = true; break; } } - if (signal_found == false) + if (signal_found == false) { + LED_A_OFF(); 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 @@ -270,8 +214,9 @@ static bool get_signalproperties(void) { AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; while (AT91C_BASE_TC0->TC_CV < T0 * 3 * EM4X50_T_TAG_FULL_PERIOD) { - volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + if (BUTTON_PRESS()) return false; + volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; if (sample > sample_max[i]) sample_max[i] = sample; @@ -285,78 +230,76 @@ static bool get_signalproperties(void) { // set global envelope variables gHigh = sample_ref + pct * (sample_max_mean - sample_ref) / 100; gLow = sample_ref - pct * (sample_max_mean - sample_ref) / 100; + + LED_A_OFF(); + return true; } -static int get_next_bit(void) { +static bool invalid_bit(void) { - // returns bit value (or EM4X50_BIT_OTHER -> no bit pattern) by evaluating - // a single sample within a bit period (given there is no LIW, ACK or NAK) - // This function is not used for decoding, it is only used for identifying - // a listen window (return value = EM4X50_BIT_OTHER) in functions + // returns true if bit is undefined by evaluating a single sample within + // a bit period (given there is no LIW, ACK or NAK) + // This function is used for identifying a listen window in functions // "find_double_listen_window" and "check_ack" // get sample at 3/4 of bit period - wait_timer(0, T0 * EM4X50_T_TAG_THREE_QUARTER_PERIOD); + wait_timer(T0 * EM4X50_T_TAG_THREE_QUARTER_PERIOD); + uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; // wait until end of bit period - wait_timer(0, T0 * EM4X50_T_TAG_QUARTER_PERIOD); + wait_timer(T0 * EM4X50_T_TAG_QUARTER_PERIOD); - // decide wether "0" or "1" - if (sample > gHigh) - return EM4X50_BIT_0; - else if (sample < gLow) - return EM4X50_BIT_1; + // bit in "undefined" state? + if (sample <= gHigh && sample >= gLow) + return true; - return EM4X50_BIT_OTHER; + return false; } static uint32_t get_pulse_length(void) { -// Dbprintf( _CYAN_("4x50 get_pulse_length A") ); - int32_t timeout = (T0 * 3 * EM4X50_T_TAG_FULL_PERIOD); // iterates pulse length (low -> high -> low) volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - while (sample > gLow && (timeout--)) { + while (sample > gLow && (timeout--)) sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - } if (timeout == 0) return 0; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG; + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; timeout = (T0 * 3 * EM4X50_T_TAG_FULL_PERIOD); - while (sample < gHigh && (timeout--)) { + while (sample < gHigh && (timeout--)) sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - } if (timeout == 0) return 0; timeout = (T0 * 3 * EM4X50_T_TAG_FULL_PERIOD); - while (sample > gLow && (timeout--)) { + while (sample > gLow && (timeout--)) sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - } if (timeout == 0) return 0; - return (uint32_t)AT91C_BASE_TC1->TC_CV; + return (uint32_t)AT91C_BASE_TC0->TC_CV; } static bool check_pulse_length(uint32_t pl, int length) { + // check if pulse length corresponds to given length - return ((pl >= T0 * (length - EM4X50_TAG_TOLERANCE)) & (pl <= T0 * (length + EM4X50_TAG_TOLERANCE))); + return ((pl >= T0 * (length - EM4X50_TAG_TOLERANCE)) && (pl <= T0 * (length + EM4X50_TAG_TOLERANCE))); + } -static void em4x50_send_bit(int bit) { +static void em4x50_reader_send_bit(int bit) { // send single bit according to EM4x50 application note and datasheet @@ -365,7 +308,7 @@ static void em4x50_send_bit(int bit) { if (bit == 0) { - // disable modulation (drop the field) for 7 cycles of carrier + // disable modulation (drops the field) for 7 cycles of carrier // period (Opt64) LOW(GPIO_SSC_DOUT); while (AT91C_BASE_TC0->TC_CV < T0 * 7); @@ -387,16 +330,16 @@ static void em4x50_send_bit(int bit) { } } -static void em4x50_send_byte(uint8_t byte) { +static void em4x50_reader_send_byte(uint8_t byte) { // send byte (without parity) for (int i = 0; i < 8; i++) - em4x50_send_bit((byte >> (7 - i)) & 1); + em4x50_reader_send_bit((byte >> (7 - i)) & 1); } -static void em4x50_send_byte_with_parity(uint8_t byte) { +static void em4x50_reader_send_byte_with_parity(uint8_t byte) { // send byte followed by its (equal) parity bit @@ -404,25 +347,30 @@ static void em4x50_send_byte_with_parity(uint8_t byte) { for (int i = 0; i < 8; i++) { bit = (byte >> (7 - i)) & 1; - em4x50_send_bit(bit); + em4x50_reader_send_bit(bit); parity ^= bit; } - em4x50_send_bit(parity); + em4x50_reader_send_bit(parity); } -static void em4x50_send_word(const uint8_t bytes[4]) { +static void em4x50_reader_send_word(const uint32_t word) { // send 32 bit word with parity bits according to EM4x50 datasheet + // word hast be sent in msb notation - for (int i = 0; i < 4; i++) - em4x50_send_byte_with_parity(bytes[i]); - + uint8_t bytes[4] = {0x0, 0x0, 0x0, 0x0}; + + for (int i = 0; i < 4; i++) { + bytes[i] = (word >> (24 - (8 * i))) & 0xFF; + em4x50_reader_send_byte_with_parity(bytes[i]); + } + // send column parities - em4x50_send_byte(bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]); + em4x50_reader_send_byte(bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]); // send final stop bit (always "0") - em4x50_send_bit(0); + em4x50_reader_send_bit(0); } static bool find_single_listen_window(void) { @@ -430,6 +378,8 @@ static bool find_single_listen_window(void) { // find single listen window int cnt_pulses = 0; + + LED_B_ON(); while (cnt_pulses < EM4X50_T_WAITING_FOR_SNGLLIW) { @@ -439,17 +389,20 @@ static bool find_single_listen_window(void) { if (check_pulse_length(get_pulse_length(), 2 * EM4X50_T_TAG_FULL_PERIOD)) { - // listen window found + // found listen window + LED_B_OFF(); return true; } } cnt_pulses++; } + LED_B_OFF(); + return false; } -static bool find_double_listen_window(bool bcommand) { +static int find_double_listen_window(bool bcommand) { // find two successive listen windows that indicate the beginning of // data transmission @@ -458,9 +411,14 @@ static bool find_double_listen_window(bool bcommand) { // -> 34 words + 34 single listen windows -> about 1600 pulses int cnt_pulses = 0; + + LED_B_ON(); while (cnt_pulses < EM4X50_T_WAITING_FOR_DBLLIW) { + if (BUTTON_PRESS()) + return PM3_EOPABORTED; + // identification of listen window is done via evaluation of // pulse lengths if (check_pulse_length(get_pulse_length(), 3 * EM4X50_T_TAG_FULL_PERIOD)) { @@ -481,35 +439,41 @@ static bool find_double_listen_window(bool bcommand) { // second window follows - sync on this to issue a command // skip the next bit... - wait_timer(FPGA_TIMER_0, T0 * EM4X50_T_TAG_FULL_PERIOD); + wait_timer(T0 * EM4X50_T_TAG_FULL_PERIOD); // ...and check if the following bit does make sense // (if not it is the correct position within the second // listen window) - if (get_next_bit() == EM4X50_BIT_OTHER) { + if (invalid_bit()) { // send RM for request mode - em4x50_send_bit(0); - em4x50_send_bit(0); + em4x50_reader_send_bit(0); + em4x50_reader_send_bit(0); - return true; + LED_B_OFF(); + + return PM3_SUCCESS; } } if (check_pulse_length(get_pulse_length(), 3 * EM4X50_T_TAG_FULL_PERIOD)) { + LED_B_OFF(); + // return although second listen window consists of one // more bit period but this period is necessary for // evaluating further pulse lengths - return true; + return PM3_SUCCESS; } } - cnt_pulses++; } + cnt_pulses++; } - return false; + LED_B_OFF(); + + return PM3_EFAILED; } static bool find_em4x50_tag(void) { @@ -519,13 +483,12 @@ static bool find_em4x50_tag(void) { return find_single_listen_window(); } -static bool request_receive_mode(void) { +static int request_receive_mode(void) { // To issue a command we have to find a listen window first. // Because identification and synchronization at the same time is not // possible when using pulse lengths a double listen window is used. - bool bcommand = true; - return find_double_listen_window(bcommand); + return find_double_listen_window(true); } static bool check_ack(bool bliw) { @@ -539,6 +502,9 @@ static bool check_ack(bool bliw) { AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; while (AT91C_BASE_TC0->TC_CV < T0 * 4 * EM4X50_T_TAG_FULL_PERIOD) { + if (BUTTON_PRESS()) + return false; + if (check_pulse_length(get_pulse_length(), 2 * EM4X50_T_TAG_FULL_PERIOD)) { // The received signal is either ACK or NAK. @@ -557,15 +523,15 @@ static bool check_ack(bool bliw) { // wait for 2 bits (remaining "bit" of ACK signal + first // "bit" of listen window) - wait_timer(FPGA_TIMER_0, T0 * 2 * EM4X50_T_TAG_FULL_PERIOD); + wait_timer(T0 * 2 * EM4X50_T_TAG_FULL_PERIOD); // check for listen window (if first bit cannot be interpreted // as a valid bit it must belong to a listen window) - if (get_next_bit() == EM4X50_BIT_OTHER) { + if (invalid_bit()) { // send RM for request mode - em4x50_send_bit(0); - em4x50_send_bit(0); + em4x50_reader_send_bit(0); + em4x50_reader_send_bit(0); return true; } @@ -581,316 +547,585 @@ static bool check_ack(bool bliw) { return false; } -static int get_word_from_bitstream(uint8_t bits[EM4X50_TAG_WORD]) { +static int get_word_from_bitstream(uint32_t *data) { // decodes one word by evaluating pulse lengths and previous bit; // word must have 45 bits in total: // 32 data bits + 4 row parity bits + 8 column parity bits + 1 stop bit - bool bbitchange = false; - int i = 0; + bool bitchange = false; + int cnt = 0; uint32_t pl = 0; + uint64_t word = 0x0; + + LED_C_ON(); + + *data = 0x0; // initial bit value depends on last pulse length of listen window pl = get_pulse_length(); if (check_pulse_length(pl, 3 * EM4X50_T_TAG_HALF_PERIOD)) { // pulse length = 1.5 - bits[0] = 1; + word = 0x1; } else if (check_pulse_length(pl, 2 * EM4X50_T_TAG_FULL_PERIOD)) { // pulse length = 2 - bits[0] = 0; - bbitchange = true; + bitchange = true; } else { // pulse length = 2.5 - bits[0] = 0; - bits[1] = 1; - i++; + word = 0x1; + cnt++; } // identify remaining bits based on pulse lengths // between two listen windows only pulse lengths of 1, 1.5 and 2 are possible - while (true) { + while (BUTTON_PRESS() == false) { - i++; + cnt++; + word <<= 1; + pl = get_pulse_length(); if (check_pulse_length(pl, EM4X50_T_TAG_FULL_PERIOD)) { // pulse length = 1 -> keep former bit value - bits[i] = bits[i - 1]; + word |= (word >> 1) & 0x1; } else if (check_pulse_length(pl, 3 * EM4X50_T_TAG_HALF_PERIOD)) { // pulse length = 1.5 -> decision on bit change - if (bbitchange) { + if (bitchange) { // if number of pulse lengths with 1.5 periods is even -> add bit - bits[i] = (bits[i - 1] == 1) ? 1 : 0; + word |= (word >> 1) & 0x1; + word <<= 1; // pulse length of 1.5 changes bit value - bits[i + 1] = (bits[i] == 1) ? 0 : 1; - i++; + word |= ((word >> 1) & 0x1) ^ 0x1; + cnt++; // next time add only one bit - bbitchange = false; + bitchange = false; } else { - bits[i] = (bits[i - 1] == 1) ? 0 : 1; + word |= ((word >> 1) & 0x1) ^ 0x1; // next time two bits have to be added - bbitchange = true; + bitchange = true; } } else if (check_pulse_length(pl, 2 * EM4X50_T_TAG_FULL_PERIOD)) { // pulse length of 2 means: adding 2 bits "01" - bits[i] = 0; - bits[i + 1] = 1; - i++; + cnt++; + + word <<= 1; + word |= 0x1; } else if (check_pulse_length(pl, 3 * EM4X50_T_TAG_FULL_PERIOD)) { - // pulse length of 3 indicates listen window -> clear last - // bit (= 0) and return - return --i; + LED_C_OFF(); + // pulse length of 3 indicates listen window -> clear last + // bit (= 0) and return (without parities) + word >>= 2; + return (extract_parities(word, data)) ? --cnt : 0; } } + + LED_C_OFF(); + + return PM3_EOPABORTED; } -//============================================================================== -// login function -//============================================================================== +static bool em4x50_sim_send_bit(uint8_t bit) { -static bool login(uint8_t password[4]) { + uint16_t check = 0; + + for (int t = 0; t < EM4X50_T_TAG_FULL_PERIOD; t++) { + + // wait until SSC_CLK goes HIGH + // used as a simple detection of a reader field? + while (!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)) { + WDT_HIT(); + if (check == 1000) { + if (BUTTON_PRESS()) + return false; + check = 0; + } + ++check; + } + + if (bit) + OPEN_COIL(); + else + SHORT_COIL(); + + check = 0; + + //wait until SSC_CLK goes LOW + while (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK) { + WDT_HIT(); + if (check == 1000) { + if (BUTTON_PRESS()) + return false; + check = 0; + } + ++check; + } + + if (t == EM4X50_T_TAG_HALF_PERIOD) + bit ^= 1; + + } + + return true; +} + +static bool em4x50_sim_send_byte(uint8_t byte) { + + // send byte + for (int i = 0; i < 8; i++) + if (!em4x50_sim_send_bit((byte >> (7 - i)) & 1)) + return false; + + return true; + +} + +static bool em4x50_sim_send_byte_with_parity(uint8_t byte) { + + uint8_t parity = 0x0; + + // send byte with parity (even) + for (int i = 0; i < 8; i++) + parity ^= (byte >> i) & 1; + + if (!em4x50_sim_send_byte(byte)) + return false;; + + if (!em4x50_sim_send_bit(parity)) + return false; + + return true; +} + +bool em4x50_sim_send_word(uint32_t word) { + + uint8_t cparity = 0x00; + + // word has tobe sent in msb, not lsb + word = reflect32(word); + + // 4 bytes each with even row parity bit + for (int i = 0; i < 4; i++) + if (!em4x50_sim_send_byte_with_parity((word >> ((3 - i) * 8)) & 0xFF)) + return false; + + // column parity + for (int i = 0; i < 8; i++) { + cparity <<= 1; + for (int j = 0; j < 4; j++) { + cparity ^= (((word >> ((3 - j) * 8)) & 0xFF) >> (7 - i)) & 1; + } + } + if (!em4x50_sim_send_byte(cparity)) + return false; + + // stop bit + if (!em4x50_sim_send_bit(0)) + return false; + + return true; +} + +bool em4x50_sim_send_listen_window(void) { + + uint16_t check = 0; + + for (int t = 0; t < 5 * EM4X50_T_TAG_FULL_PERIOD; t++) { + + // wait until SSC_CLK goes HIGH + while (!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)) { + WDT_HIT(); + if (check == 1000) { + if (BUTTON_PRESS()) + return false; + check = 0; + } + ++check; + } + + if (t >= 4 * EM4X50_T_TAG_FULL_PERIOD) + SHORT_COIL(); + else if (t >= 3 * EM4X50_T_TAG_FULL_PERIOD) + OPEN_COIL(); + else if (t >= EM4X50_T_TAG_FULL_PERIOD) + SHORT_COIL(); + else if (t >= EM4X50_T_TAG_HALF_PERIOD) + OPEN_COIL(); + else + SHORT_COIL(); + + check = 0; + + // wait until SSC_CLK goes LOW + while (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK) { + WDT_HIT(); + if (check == 1000) { + if (BUTTON_PRESS()) + return false; + check = 0; + } + ++check; + } + } + + return true; +} + +static bool login(uint32_t password) { // simple login to EM4x50, // used in operations that require authentication - if (request_receive_mode()) { + if (request_receive_mode() == PM3_SUCCESS) { // send login command - em4x50_send_byte_with_parity(EM4X50_COMMAND_LOGIN); + em4x50_reader_send_byte_with_parity(EM4X50_COMMAND_LOGIN); // send password - em4x50_send_word(password); + em4x50_reader_send_word(password); + + wait_timer(T0 * EM4X50_T_TAG_TPP); // check if ACK is returned if (check_ack(false)) - return true; + return PM3_SUCCESS; } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("error in command request"); } - return false; + return PM3_EFAILED; } -//============================================================================== -// reset function -//============================================================================== +static bool brute(uint32_t start, uint32_t stop, uint32_t *pwd) { -static bool reset(void) { + // searching for password in given range + + bool pwd_found = false; + int cnt = 0; + + for (*pwd = start; *pwd <= stop; (*pwd)++) { + + if (login(*pwd) == PM3_SUCCESS) { + + pwd_found = true; + + // to be safe login 5 more times + for (int i = 0; i < 5; i++) { + if (login(*pwd) != PM3_SUCCESS) { + pwd_found = false; + break; + } + } + + if (pwd_found) + break; + } + + // print password every 500 iterations + if ((++cnt % 500) == 0) { + + // print header + if (cnt == 500) { + Dbprintf("|---------+------------+------------|"); + Dbprintf("| no. | pwd (msb) | pwd (lsb) |"); + Dbprintf("|---------+------------+------------|"); + } + + // print data + Dbprintf("|%8i | 0x%08x | 0x%08x |", cnt, reflect32(*pwd), *pwd); + } + + if (BUTTON_PRESS()) + break; + + } + + // print footer + if (cnt >= 500) + Dbprintf("|---------+------------+------------|"); + + return pwd_found; +} + +void em4x50_login(uint32_t *password) { + + // login into EM4x50 + + uint8_t status = PM3_EFAILED; + + em4x50_setup_read(); + + // set gHigh and gLow + if (get_signalproperties() && find_em4x50_tag()) + status = login(*password); + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_LOGIN, status, 0, 0); +} + +void em4x50_brute(em4x50_data_t *etd) { + + // envoke password search + + bool bsuccess = false; + uint32_t pwd = 0x0; + + em4x50_setup_read(); + + if (get_signalproperties() && find_em4x50_tag()) + bsuccess = brute(etd->password1, etd->password2, &pwd); + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_BRUTE, bsuccess, (uint8_t *)(&pwd), 32); +} + +void em4x50_chk(uint8_t *filename) { + + // check passwords from dictionary content in flash memory + + int status = PM3_EFAILED; + uint32_t pwd = 0x0; + +#ifdef WITH_FLASH + + BigBuf_free(); + + int changed = rdv40_spiffs_lazy_mount(); + uint16_t pwd_count = 0; + uint32_t size = size_in_spiffs((char *)filename); + pwd_count = size / 4; + uint8_t *pwds = BigBuf_malloc(size); + + rdv40_spiffs_read_as_filetype((char *)filename, pwds, size, RDV40_SPIFFS_SAFETY_SAFE); + + if (changed) + rdv40_spiffs_lazy_unmount(); + + em4x50_setup_read(); + + // set gHigh and gLow + if (get_signalproperties() && find_em4x50_tag()) { + + // try to login with current password + for (int i = 0; i < pwd_count; i++) { + + // manual interruption + if (BUTTON_PRESS()) { + status = PM3_EOPABORTED; + break; + } + + // get next password + pwd = 0x0; + for (int j = 0; j < 4; j++) + pwd |= (*(pwds + 4 * i + j)) << ((3 - j) * 8); + + if ((status = login(pwd)) == PM3_SUCCESS) + break; + } + } + + BigBuf_free(); + +#endif + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_CHK, status, (uint8_t *)&pwd, 32); +} + +static int reset(void) { // resets EM4x50 tag (used by write function) - if (request_receive_mode()) { + if (request_receive_mode() == PM3_SUCCESS) { - // send login command - em4x50_send_byte_with_parity(EM4X50_COMMAND_RESET); + // send reset command + em4x50_reader_send_byte_with_parity(EM4X50_COMMAND_RESET); if (check_ack(false)) - return true; + return PM3_SUCCESS; } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("error in command request"); } - return false; + return PM3_EFAILED; } -//============================================================================== -// read functions -//============================================================================== - -static bool standard_read(int *now) { +static int standard_read(int *now, uint32_t *words) { // reads data that tag transmits when exposed to reader field // (standard read mode); number of read words is saved in - int fwr = *now; - uint8_t bits[EM4X50_TAG_WORD] = {0}; + int fwr = *now, res = PM3_EFAILED; // start with the identification of two successive listening windows - if (find_double_listen_window(false)) { + if ((res = find_double_listen_window(false)) == PM3_SUCCESS) { // read and save words until following double listen window is detected - while (get_word_from_bitstream(bits) == EM4X50_TAG_WORD) - save_word((*now)++, bits); + while ((res = get_word_from_bitstream(&words[*now])) == EM4X50_TAG_WORD) + (*now)++; // number of detected words *now -= fwr; - return true; - } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("didn't find a listen window"); } - return false; + return res; } -static bool selective_read(uint8_t addresses[4]) { +static int selective_read(uint32_t addresses, uint32_t *words) { - // reads from "first word read" (fwr = addresses[3]) to "last word read" - // (lwr = addresses[2]) + // reads from "first word read" (fwr) to "last word read" (lwr) // result is verified by "standard read mode" - int fwr = addresses[3]; // first word read - int lwr = addresses[2]; // last word read - int now = fwr; // number of words + int status = PM3_EFAILED; + uint8_t fwr = addresses & 0xFF; // first word read (first byte) + uint8_t lwr = (addresses >> 8) & 0xFF; // last word read (second byte) + int now = fwr; // number of words - if (request_receive_mode()) { + if (request_receive_mode() == PM3_SUCCESS) { // send selective read command - em4x50_send_byte_with_parity(EM4X50_COMMAND_SELECTIVE_READ); + em4x50_reader_send_byte_with_parity(EM4X50_COMMAND_SELECTIVE_READ); // send address data - em4x50_send_word(addresses); + em4x50_reader_send_word(addresses); // look for ACK sequence if (check_ack(false)) // save and verify via standard read mode (compare number of words) - if (standard_read(&now)) + if ((status = standard_read(&now, words)) == PM3_SUCCESS) if (now == (lwr - fwr + 1)) - return true; + return status; } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("error in command request"); } - return false; + return status; +} + +void em4x50_read(em4x50_data_t *etd) { + + // reads by using "selective read mode" -> bidirectional communication + + bool blogin = true; + int status = PM3_EFAILED; + uint32_t words[EM4X50_NO_WORDS] = {0x0}; + + em4x50_setup_read(); + + // set gHigh and gLow + if (get_signalproperties() && find_em4x50_tag()) { + + // try to login with given password + if (etd->pwd_given) + blogin = (login(etd->password1) == PM3_SUCCESS); + + // only one word has to be read -> first word read = last word read + if (blogin) + status = selective_read(etd->addresses, words); + } + + LOW(GPIO_SSC_DOUT); + lf_finalize(); + reply_ng(CMD_LF_EM4X50_READ, status, (uint8_t *)words, 136); } void em4x50_info(em4x50_data_t *etd) { // collects as much information as possible via selective read mode - // if no password is given -> try with standard password "0x00000000" - // otherwise continue without login - bool bsuccess = false, blogin = false; - uint8_t status = 0; - uint8_t addresses[] = {0x00, 0x00, 0x21, 0x00}; // fwr = 0, lwr = 33 - uint8_t password[] = {0x00, 0x00, 0x00, 0x00}; // default password + bool blogin = true; + int status = PM3_EFAILED; + uint32_t addresses = 0x00002100; // read from fwr = 0 to lwr = 33 (0x21) + uint32_t words[EM4X50_NO_WORDS] = {0x0}; - init_tag(); em4x50_setup_read(); // set gHigh and gLow if (get_signalproperties() && find_em4x50_tag()) { - if (etd->pwd_given) { + // login with given password + if (etd->pwd_given) + blogin = (login(etd->password1) == PM3_SUCCESS); - // try to login with given password - blogin = login(etd->password); - - } else { - - // if no password is given, try to login with "0x00000000" - blogin = login(password); - - } - - bsuccess = selective_read(addresses); + if (blogin) + status = selective_read(addresses, words); } - status = (bsuccess << 1) + blogin; - lf_finalize(); - reply_ng(CMD_LF_EM4X50_INFO, status, (uint8_t *)tag.sectors, 238); + reply_ng(CMD_LF_EM4X50_INFO, status, (uint8_t *)words, 136); } -void em4x50_read(em4x50_data_t *etd) { +void em4x50_reader(void) { - // reads in two different ways: - // - using "selective read mode" -> bidirectional communication - // - using "standard read mode" -> unidirectional communication (read - // data that tag transmits "voluntarily") + // reads data that tag transmits "voluntarily" -> standard read mode - bool bsuccess = false, blogin = false; int now = 0; - uint8_t status = 0; - uint8_t addresses[] = {0x00, 0x00, 0x00, 0x00}; + uint32_t words[EM4X50_NO_WORDS] = {0x0}; - init_tag(); em4x50_setup_read(); // set gHigh and gLow - if (get_signalproperties() && find_em4x50_tag()) { - - if (etd->addr_given) { - - // selective read mode - - // try to login with given password - if (etd->pwd_given) - blogin = login(etd->password); - - // only one word has to be read -> first word read = last word read - addresses[2] = addresses[3] = etd->address; - bsuccess = selective_read(addresses); - - } else { - - // standard read mode - bsuccess = standard_read(&now); - - } - } - - status = (now << 2) + (bsuccess << 1) + blogin; + if (get_signalproperties() && find_em4x50_tag()) + standard_read(&now, words); + LOW(GPIO_SSC_DOUT); lf_finalize(); - reply_ng(CMD_LF_EM4X50_READ, status, (uint8_t *)tag.sectors, 238); + reply_ng(CMD_LF_EM4X50_READER, now, (uint8_t *)words, 4 * now); } -//============================================================================== -// write functions -//============================================================================== -static int write(uint8_t word[4], uint8_t address) { +static int write(uint32_t word, uint32_t addresses) { - // writes to specified
- - if (request_receive_mode()) { + // writes to specified + + if (request_receive_mode() == PM3_SUCCESS) { // send write command - em4x50_send_byte_with_parity(EM4X50_COMMAND_WRITE); + em4x50_reader_send_byte_with_parity(EM4X50_COMMAND_WRITE); // send address data - em4x50_send_byte_with_parity(address); + em4x50_reader_send_byte_with_parity(addresses & 0xFF); // send data - em4x50_send_word(word); + em4x50_reader_send_word(word); if (tearoff_hook() == PM3_ETEAROFF) { // tearoff occurred reply_ng(CMD_LF_EM4X50_WRITE, PM3_ETEAROFF, NULL, 0); return PM3_ETEAROFF; } else { - + // wait for T0 * EM4X50_T_TAG_TWA (write access time) - wait_timer(FPGA_TIMER_0, T0 * EM4X50_T_TAG_TWA); + wait_timer(T0 * EM4X50_T_TAG_TWA); // look for ACK sequence if (check_ack(false)) { @@ -899,29 +1134,27 @@ static int write(uint8_t word[4], uint8_t address) { // for saving data and should return with ACK if (check_ack(false)) return PM3_SUCCESS; - } } - } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("error in command request"); } - return PM3_ESOFT; + return PM3_EFAILED; } -static int write_password(uint8_t password[4], uint8_t new_password[4]) { +static int write_password(uint32_t password, uint32_t new_password) { // changes password from to - if (request_receive_mode()) { + if (request_receive_mode() == PM3_SUCCESS) { // send write password command - em4x50_send_byte_with_parity(EM4X50_COMMAND_WRITE_PASSWORD); + em4x50_reader_send_byte_with_parity(EM4X50_COMMAND_WRITE_PASSWORD); // send address data - em4x50_send_word(password); + em4x50_reader_send_word(password); if (tearoff_hook() == PM3_ETEAROFF) { // tearoff occurred reply_ng(CMD_LF_EM4X50_WRITE, PM3_ETEAROFF, NULL, 0); @@ -929,178 +1162,176 @@ static int write_password(uint8_t password[4], uint8_t new_password[4]) { } else { // wait for T0 * EM4x50_T_TAG_TPP (processing pause time) - wait_timer(FPGA_TIMER_0, T0 * EM4X50_T_TAG_TPP); + wait_timer(T0 * EM4X50_T_TAG_TPP); // look for ACK sequence and send rm request // during following listen window if (check_ack(true)) { // send new password - em4x50_send_word(new_password); + em4x50_reader_send_word(new_password); // wait for T0 * EM4X50_T_TAG_TWA (write access time) - wait_timer(FPGA_TIMER_0, T0 * EM4X50_T_TAG_TWA); + wait_timer(T0 * EM4X50_T_TAG_TWA); if (check_ack(false)) if (check_ack(false)) return PM3_SUCCESS; - } } - } else { if (DBGLEVEL >= DBG_DEBUG) Dbprintf("error in command request"); } - return PM3_ESOFT; + return PM3_EFAILED; } void em4x50_write(em4x50_data_t *etd) { // write operation process for EM4x50 tag, // single word is written to given address, verified by selective read operation + // wrong password -> return with PM3_EFAILED - bool bsuccess = false, blogin = false; - uint8_t status = 0; - uint8_t word[4] = {0x00, 0x00, 0x00, 0x00}; - uint8_t addresses[4] = {0x00, 0x00, 0x00, 0x00}; + int status = PM3_EFAILED; + uint32_t words[EM4X50_NO_WORDS] = {0x0}; - init_tag(); em4x50_setup_read(); // set gHigh and gLow if (get_signalproperties() && find_em4x50_tag()) { - // reorder word according to datasheet - msb2lsb_word(etd->word); - // if password is given try to login first + status = PM3_SUCCESS; if (etd->pwd_given) - blogin = login(etd->password); + status = login(etd->password1); - // write word to given address - int res = write(etd->word, etd->address); - if (res == PM3_ETEAROFF) { - lf_finalize(); - return; - } + if (status == PM3_SUCCESS) { - if (res == PM3_SUCCESS) { - // to verify result reset EM4x50 - if (reset()) { + // write word to given address + status = write(etd->word, etd->addresses); + if (status == PM3_ETEAROFF) { + lf_finalize(); + return; + } - // if password is given login - if (etd->pwd_given) - blogin &= login(etd->password); + if (status == PM3_SUCCESS) { - // call a selective read - addresses[2] = addresses[3] = etd->address; - if (selective_read(addresses)) { + // to verify result reset EM4x50 + status = reset(); + if (status == PM3_SUCCESS) { - // compare with given word - word[0] = tag.sectors[etd->address][0]; - word[1] = tag.sectors[etd->address][1]; - word[2] = tag.sectors[etd->address][2]; - word[3] = tag.sectors[etd->address][3]; - msb2lsb_word(word); + // if password is given renew login after reset + if (etd->pwd_given) + status = login(etd->password1); + + if (status == PM3_SUCCESS) { - bsuccess = true; - for (int i = 0; i < 4; i++) - bsuccess &= (word[i] == etd->word[i]) ? true : false; + // call a selective read + status = selective_read(etd->addresses, words); + if (status == PM3_SUCCESS) { + // compare result with given word + if (words[etd->addresses & 0xFF] != reflect32(etd->word)) + status = PM3_EFAILED; + } + } } } } } - status = (bsuccess << 1) + blogin; lf_finalize(); - reply_ng(CMD_LF_EM4X50_WRITE, status, (uint8_t *)tag.sectors, 238); + reply_ng(CMD_LF_EM4X50_WRITE, status, (uint8_t *)words, 136); } -void em4x50_write_password(em4x50_data_t *etd) { +void em4x50_writepwd(em4x50_data_t *etd) { // simple change of password + + int status = PM3_EFAILED; - bool bsuccess = false; - - init_tag(); em4x50_setup_read(); // set gHigh and gLow if (get_signalproperties() && find_em4x50_tag()) { // login and change password - if (login(etd->password)) { + if (login(etd->password1) == PM3_SUCCESS) { - int res = write_password(etd->password, etd->new_password); - if (res == PM3_ETEAROFF) { + status = write_password(etd->password1, etd->password2); + if (status == PM3_ETEAROFF) { lf_finalize(); return; } - bsuccess = (res == PM3_SUCCESS); } } lf_finalize(); - reply_ng(CMD_LF_EM4X50_WRITE_PASSWORD, bsuccess, 0, 0); + reply_ng(CMD_LF_EM4X50_WRITEPWD, status, 0, 0); } -void em4x50_wipe(em4x50_data_t *etd) { +void em4x50_sim(uint8_t *filename) { - // set all data of EM4x50 tag to 0x0 including password + // simulate uploaded data in emulator memory + // (currently simulation allows only a one-way communication) - bool bsuccess = false; - uint8_t zero[4] = {0, 0, 0, 0}; - uint8_t addresses[4] = {0, 0, EM4X50_NO_WORDS - 3, 1}; + int status = PM3_SUCCESS; + uint8_t *em4x50_mem = BigBuf_get_EM_addr(); + uint32_t words[EM4X50_NO_WORDS] = {0x0}; + +#ifdef WITH_FLASH - init_tag(); - em4x50_setup_read(); + if (strlen((char *)filename) != 0) { + + BigBuf_free(); - // set gHigh and gLow - if (get_signalproperties() && find_em4x50_tag()) { + int changed = rdv40_spiffs_lazy_mount(); + uint32_t size = size_in_spiffs((char *)filename); + em4x50_mem = BigBuf_malloc(size); - // login first - if (login(etd->password)) { + rdv40_spiffs_read_as_filetype((char *)filename, em4x50_mem, size, RDV40_SPIFFS_SAFETY_SAFE); - // write 0x0 to each address but ignore addresses - // 0 -> password, 32 -> serial, 33 -> uid - // writing 34 words takes about 3.6 seconds -> high timeout needed - for (int i = 1; i <= EM4X50_NO_WORDS - 3; i++) - write(zero, i); + if (changed) + rdv40_spiffs_lazy_unmount(); + } - // to verify result reset EM4x50 - if (reset()) { +#endif - // login not necessary because protected word has been set to 0 - // -> no read protected words - // -> selective read can be called immediately - if (selective_read(addresses)) { + for (int i = 0; i < EM4X50_NO_WORDS; i++) + words[i] = reflect32(bytes_to_num(em4x50_mem + (i * 4), 4)); + + // only if valid em4x50 data (e.g. uid == serial) + if (words[EM4X50_DEVICE_SERIAL] != words[EM4X50_DEVICE_ID]) { - // check if everything is zero - bsuccess = true; - for (int i = 1; i <= EM4X50_NO_WORDS - 3; i++) - for (int j = 0; j < 4; j++) - bsuccess &= (tag.sectors[i][j] == 0) ? true : false; + // extract control data + int fwr = words[CONFIG_BLOCK] & 0xFF; // first word read + int lwr = (words[CONFIG_BLOCK] >> 8) & 0xFF; // last word read + // extract protection data + int fwrp = words[EM4X50_PROTECTION] & 0xFF; // first word read protected + int lwrp = (words[EM4X50_PROTECTION] >> 8) & 0xFF; // last word read protected - } + em4x50_setup_sim(); - if (bsuccess) { + while (!BUTTON_PRESS()) { + + WDT_HIT(); + em4x50_sim_send_listen_window(); + for (int i = fwr; i <= lwr; i++) { - // so far everything is fine - // last task: reset password - if (login(etd->password)) - bsuccess = write_password(etd->password, zero); + em4x50_sim_send_listen_window(); - // verify by login with new password - if (bsuccess) - bsuccess = login(zero); - } + if ((i >= fwrp) && (i <= lwrp)) + em4x50_sim_send_word(0x00); + else + em4x50_sim_send_word(words[i]); } } + } else { + status = PM3_ENODATA; } - + + BigBuf_free(); lf_finalize(); - reply_ng(CMD_LF_EM4X50_WIPE, bsuccess, (uint8_t *)tag.sectors, 238); + reply_ng(CMD_LF_EM4X50_SIM, status, 0, 0); } diff --git a/armsrc/em4x50.h b/armsrc/em4x50.h index f9f1375f2..12f0a64d8 100644 --- a/armsrc/em4x50.h +++ b/armsrc/em4x50.h @@ -13,14 +13,19 @@ #include "../include/em4x50.h" -typedef struct { - uint8_t sectors[34][7]; -} em4x50_tag_t; +int em4x50_standalone_read(uint32_t *words); +int em4x50_standalone_brute(uint32_t start, uint32_t stop, uint32_t *pwd); +bool em4x50_sim_send_listen_window(void); +bool em4x50_sim_send_word(uint32_t word); void em4x50_info(em4x50_data_t *etd); void em4x50_write(em4x50_data_t *etd); -void em4x50_write_password(em4x50_data_t *etd); +void em4x50_writepwd(em4x50_data_t *etd); void em4x50_read(em4x50_data_t *etd); -void em4x50_wipe(em4x50_data_t *etd); +void em4x50_brute(em4x50_data_t *etd); +void em4x50_login(uint32_t *password); +void em4x50_sim(uint8_t *filename); +void em4x50_reader(void); +void em4x50_chk(uint8_t *filename); #endif /* EM4X50_H */ diff --git a/armsrc/lfadc.h b/armsrc/lfadc.h index 1c8a4211f..66c6da534 100644 --- a/armsrc/lfadc.h +++ b/armsrc/lfadc.h @@ -31,6 +31,7 @@ void lf_wait_periods(size_t periods); void lf_init(bool reader, bool simulate); void lf_finalize(void); size_t lf_detect_field_drop(size_t max); + bool lf_manchester_send_bytes(const uint8_t *frame, size_t frame_len); void lf_modulation(bool modulation); diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index be0b17bb1..6327f600d 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -10,153 +10,26 @@ // Low frequency EM4x50 commands //----------------------------------------------------------------------------- +#include "cliparser.h" #include "cmdlfem4x50.h" #include #include "cmdparser.h" // command_t #include "fileutils.h" -#include "comms.h" #include "commonutil.h" -#include "em4x50.h" +#include "pmflash.h" +#include "cmdflashmemspiffs.h" + +#define BYTES2UINT32(x) ((x[0] << 24) | (x[1] << 16) | (x[2] << 8) | (x[3])) static int CmdHelp(const char *Cmd); -static int usage_lf_em4x50_info(void) { - PrintAndLogEx(NORMAL, "Read all information of EM4x50. Tag must be on antenna."); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_info [h] [v] [p ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " v - verbose output"); - PrintAndLogEx(NORMAL, " p - password (hex) (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_info")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_info p fa225de1")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_info v p fa225de1")); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; -} -static int usage_lf_em4x50_write(void) { - PrintAndLogEx(NORMAL, "Write EM4x50 word. Tag must be on antenna. "); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_write [h] [a
] [w ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " a - memory address to write to (dec)"); - PrintAndLogEx(NORMAL, " w - word to write (hex)"); - PrintAndLogEx(NORMAL, " p - password (hex) (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_write a 3 w deadc0de")); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; -} -static int usage_lf_em4x50_write_password(void) { - PrintAndLogEx(NORMAL, "Write EM4x50 password. Tag must be on antenna. "); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_write_password [h] [p ] [n ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " p - password (hex)"); - PrintAndLogEx(NORMAL, " n - new password (hex)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_write_password p 11223344 n 01020304")); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; -} -static int usage_lf_em4x50_read(void) { - PrintAndLogEx(NORMAL, "Read EM4x50 word(s). Tag must be on antenna."); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_read [h] [a
] [p ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " a - memory address to read (dec) (optional)"); - PrintAndLogEx(NORMAL, " p - password (hex) (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_read")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_read a 2 p 00000000")); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; -} -static int usage_lf_em4x50_dump(void) { - PrintAndLogEx(NORMAL, "Dump EM4x50 tag. Tag must be on antenna."); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_dump [h] [f ] [p ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " f - override filename prefix (optional). Default is based on UID"); - PrintAndLogEx(NORMAL, " p - password (hex) (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_dump")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_dump p 11223344")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_dump f card_nnn p 11223344")); - return PM3_SUCCESS; -} -static int usage_lf_em4x50_wipe(void) { - PrintAndLogEx(NORMAL, "Wipe data from EM4x50 tag. Tag must be on antenna. "); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 4x50_wipe [h] [p ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " p - password (hex)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 4x50_wipe p 11223344")); - PrintAndLogEx(NORMAL, ""); - return PM3_SUCCESS; -} +static void prepare_result(const uint8_t *data, int fwr, int lwr, em4x50_word_t *words) { -static void prepare_result(const uint8_t *byte, int fwr, int lwr, em4x50_word_t *words) { + // restructure received result in "em4x50_word_t" structure - // restructure received result in "em4x50_word_t" structure and check all - // parities including stop bit; result of each check is stored in structure - - int p = 0, c[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - - for (int i = fwr; i <= lwr; i++) { - - words[i].stopparity = true; - words[i].parity = true; - - for (int j = 0; j < 8; j++) - c[j] = 0; - - for (int j = 0; j < 4; j++) { - words[i].byte[j] = byte[i * 7 + j]; - words[i].row_parity[j] = (byte[i * 7 + 4] >> (3 - j)) & 1; - - // collect parities - p = 0; - - for (int k = 0; k < 8; k++) { - - // row parity - p ^= (words[i].byte[j] >> k) & 1; - - // column parity - c[k] ^= (words[i].byte[j] >> (7 - k)) & 1; - } - - // check row parities - words[i].rparity[j] = (words[i].row_parity[j] == p) ? true : false; - - if (!words[i].rparity[j]) - words[i].parity = false; - } - - // check column parities - words[i].col_parity = byte[i * 7 + 5]; - - for (int j = 0; j < 8; j++) { - words[i].cparity[j] = (((words[i].col_parity >> (7 - j)) & 1) == c[j]) ? true : false; - - if (!words[i].cparity[j]) - words[i].parity = false; - } - - // check stop bit - words[i].stopbit = byte[i * 7 + 6] & 1; - - if (words[i].stopbit == 1) - words[i].stopparity = false; - } + for (int i = fwr; i <= lwr; i++) + for (int j = 0; j < 4; j++) + words[i].byte[j] = data[i * 4 + (3 - j)]; } static void print_result(const em4x50_word_t *words, int fwr, int lwr) { @@ -207,7 +80,7 @@ static void print_result(const em4x50_word_t *words, int fwr, int lwr) { PrintAndLogEx(INFO, "----+-------------+-------------+--------------------"); } -static void print_info_result(uint8_t *data, bool verbose) { +static void print_info_result(uint8_t *data) { // display all information of info result in structured format em4x50_word_t words[EM4X50_NO_WORDS]; @@ -251,270 +124,376 @@ static void print_info_result(uint8_t *data, bool verbose) { PrintAndLogEx(NORMAL, ""); } +static int em4x50_load_file(const char *filename, uint8_t *data, size_t data_len, size_t *bytes_read) { + + // read data from dump file; file type is derived from file name extension + + int res = 0; + uint32_t serial = 0x0, device_id = 0x0; + + if (str_endswith(filename, ".eml")) + res = loadFileEML(filename, data, bytes_read) != PM3_SUCCESS; + else if (str_endswith(filename, ".json")) + res = loadFileJSON(filename, data, data_len, bytes_read, NULL); + else + res = loadFile(filename, ".bin", data, data_len, bytes_read); + + if ((res != PM3_SUCCESS) && (*bytes_read != DUMP_FILESIZE)) + return PM3_EFILE; + + // valid em4x50 data? + serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); + device_id = bytes_to_num(data + 4 * EM4X50_DEVICE_ID, 4); + if (serial == device_id) { + PrintAndLogEx(WARNING, "No valid em4x50 data in file %s.", filename); + return PM3_ENODATA; + } + + return PM3_SUCCESS; +} + +static void em4x50_seteml(uint8_t *src, uint32_t offset, uint32_t numofbytes) { + + // fast push mode + conn.block_after_ACK = true; + for (size_t i = offset; i < numofbytes; i += PM3_CMD_DATA_SIZE) { + + size_t len = MIN((numofbytes - i), PM3_CMD_DATA_SIZE); + if (len == numofbytes - i) { + // Disable fast mode on last packet + conn.block_after_ACK = false; + } + clearCommandBuffer(); + SendCommandOLD(CMD_LF_EM4X50_ESET, i, len, 0, src + i, len); + } +} + +int CmdEM4x50ELoad(const char *Cmd) { + + int slen = 0; + size_t bytes_read = 0; + char filename[FILE_PATH_SIZE] = {0}; + uint8_t data[DUMP_FILESIZE] = {0x0}; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 eload", + "Loads EM4x50 tag dump into emulator memory on device.", + "lf em 4x50 eload -f mydump.bin -> uploads bin file ./mydump.bin\n" + "lf em 4x50 eload -f mydump.eml -> uploads eml file ./mydump.eml\n" + "lf em 4x50 eload -f mydump.json -> uploads json file ./mydump.json\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "filename", "", "dump filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &slen); + CLIParserFree(ctx); + + // read data from dump file; file type has to be "bin", "eml" or "json" + if (em4x50_load_file(filename, data, DUMP_FILESIZE, &bytes_read) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Read error"); + return PM3_EFILE; + } + + // upload to emulator memory + PrintAndLogEx(INFO, "Uploading dump " _YELLOW_("%s") " to emulator memory", filename); + em4x50_seteml(data, 0, DUMP_FILESIZE); + + PrintAndLogEx(INFO, "Done"); + return PM3_SUCCESS; +} + +int CmdEM4x50ESave(const char *Cmd) { + + int slen = 0; + uint32_t serial = 0x0, device_id = 0x0; + char filename[FILE_PATH_SIZE] = {0}; + char *fptr = filename; + uint8_t data[DUMP_FILESIZE] = {0x0}; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 esave", + "Saves bin/eml/json dump file of emulator memory.", + "lf em 4x50 esave -> use UID as filename\n" + "lf em 4x50 esave -f mydump.bin -> saves to bin file ./mydump.bin\n" + "lf em 4x50 esave -f mydump.eml -> saves to eml file ./mydump.eml\n" + "lf em 4x50 esave -f mydump.json -> saves to json file ./mydump.json\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "data filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &slen); + CLIParserFree(ctx); + + // download emulator memory + PrintAndLogEx(SUCCESS, "Reading emulator memory..."); + if (!GetFromDevice(BIG_BUF_EML, data, DUMP_FILESIZE, 0, NULL, 0, NULL, 2500, false)) { + PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); + return PM3_ETIMEOUT; + } + + // valid em4x50 data? + serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); + device_id = bytes_to_num(data + 4 * EM4X50_DEVICE_ID, 4); + if (serial == device_id) { + PrintAndLogEx(WARNING, "No valid em4x50 data in flash memory."); + return PM3_ENODATA; + } + + // user supplied filename? + if (slen == 0) { + PrintAndLogEx(INFO, "Using UID as filename"); + fptr += snprintf(fptr, sizeof(filename), "lf-4x50-"); + FillFileNameByUID(fptr, (uint8_t *)&data[4 * EM4X50_DEVICE_ID], "-dump", 4); + } + + saveFile(filename, ".bin", data, DUMP_FILESIZE); + saveFileEML(filename, data, DUMP_FILESIZE, 4); + saveFileJSON(filename, jsfEM4x50, data, DUMP_FILESIZE, NULL); + return PM3_SUCCESS; +} + +int CmdEM4x50Login(const char *Cmd) { + + int pwdLen = 0; + uint8_t pwd[4] = {0x0}; + uint32_t password = 0x0; + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 login", + "Login into EM4x50 tag.", + "lf em 4x50 login -p 12345678 -< login with password 12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd, &pwdLen); + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + password = BYTES2UINT32(pwd); + } + + CLIParserFree(ctx); + + // start + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_LOGIN, (uint8_t *)&password, sizeof(password)); + WaitForResponse(CMD_LF_EM4X50_LOGIN, &resp); + + // print response + if (resp.status == PM3_SUCCESS) + PrintAndLogEx(SUCCESS, "Login " _GREEN_("ok")); + else + PrintAndLogEx(FAILED, "Login " _RED_("failed")); + + return resp.status; +} + +int CmdEM4x50Brute(const char *Cmd) { + + const int speed = 27; // 27 passwords/second (empirical value) + int no_iter = 0, dur_h = 0, dur_m = 0, dur_s = 0; + + int pwd1Len = 0, pwd2Len = 0; + uint8_t pwd1[4] = {0x0}, pwd2[4] = {0x0}; + em4x50_data_t etd; + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 brute", + "Tries to bruteforce the password of a EM4x50. Function can be stopped by pressing pm3 button.", + "lf em 4x50 brute -f 12330000 -l 12340000 -> tries passwords from 0x12330000 to 0x1234000000\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "fp", "", "first password (start), 4 bytes, lsb"), + arg_str1("l", "lp", "", "last password (stop), 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd1, &pwd1Len); + CLIGetHexWithReturn(ctx, 2, pwd2, &pwd2Len); + + if (pwd1Len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd1Len); + return PM3_EINVARG; + } else if (pwd2Len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd2Len); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd1); + etd.password2 = BYTES2UINT32(pwd2); + } + + CLIParserFree(ctx); + + // print some information + no_iter = etd.password2 - etd.password1 + 1; + dur_s = no_iter / speed; + dur_h = dur_s / 3600; + dur_m = (dur_s - dur_h * 3600) / 60; + dur_s -= dur_h * 3600 + dur_m * 60; + PrintAndLogEx(INFO, "Trying %i passwords in range [0x%08x, 0x%08x]", + no_iter, etd.password1, etd.password2); + PrintAndLogEx(INFO, "Estimated duration: %ih%im%is", dur_h, dur_m, dur_s); + + // start + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_BRUTE, (uint8_t *)&etd, sizeof(etd)); + WaitForResponse(CMD_LF_EM4X50_BRUTE, &resp); + + // print response + if ((bool)resp.status) + PrintAndLogEx(SUCCESS, "Password " _GREEN_("found") ": 0x%08x", resp.data.asDwords[0]); + else + PrintAndLogEx(FAILED, "Password: " _RED_("not found")); + + return PM3_SUCCESS; +} + +int CmdEM4x50Chk(const char *Cmd) { + + // upload passwords from given dictionary to device and start check; + // if no filename is given dictionary "t55xx_default_pwds.dic" is used + + int status = PM3_EFAILED; + int keyblock = 2000; // block with 2000 bytes -> 500 keys + int res = 0, slen = 0, bytes_remaining = 0; + size_t datalen = 0; + uint8_t data[FLASH_MEM_MAX_SIZE] = {0x0}; + uint8_t *keys = data; + uint32_t key_count = 0; + uint8_t destfn[32] = "em4x50_chk.bin"; + char filename[FILE_PATH_SIZE] = {0}; + + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 chk", + "Dictionary attack against EM4x50.", + "lf em 4x50 chk -> uses T55xx default dictionary\n" + "lf em 4x50 chk -f my.dic -> uses dictionary ./my.dic\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "dictionary filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &slen); + CLIParserFree(ctx); + + // no filename -> default = t55xx_default_pwds + if (strlen(filename) == 0) { + snprintf(filename, sizeof(filename), "t55xx_default_pwds"); + PrintAndLogEx(INFO, "treating file as T55xx keys"); + } + + res = loadFileDICTIONARY(filename, data, &datalen, 4, &key_count); + if (res || !key_count) + return PM3_EFILE; + + PrintAndLogEx(INFO, "You can cancel this operation by pressing the pm3 button"); + + if (IfPm3Flash() == false) { + PrintAndLogEx(WARNING, "no flash memory available"); + return PM3_EFLASH; + } + + bytes_remaining = datalen; + while (bytes_remaining > 0) { + + PrintAndLogEx(INPLACE, "Remaining keys: %i ", bytes_remaining / 4); + + // upload to flash. + datalen = MIN(bytes_remaining, keyblock); + res = flashmem_spiffs_load(destfn, keys, datalen); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "SPIFFS upload failed"); + return res; + } + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_CHK, destfn, sizeof(destfn)); + WaitForResponseTimeoutW(CMD_LF_EM4X50_CHK, &resp, -1, false); + + status = resp.status; + if ((status == PM3_SUCCESS) || (status == PM3_EOPABORTED)) + break; + + bytes_remaining -= keyblock; + keys += keyblock; + } + + // print response + if (status == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Key " _GREEN_("found: %02x %02x %02x %02x"), + resp.data.asBytes[3], + resp.data.asBytes[2], + resp.data.asBytes[1], + resp.data.asBytes[0] + ); + } else { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "No key found"); + } + + PrintAndLogEx(INFO, "Done"); + return PM3_SUCCESS; +} + //quick test for EM4x50 tag bool detect_4x50_block(void) { em4x50_data_t etd = { .pwd_given = false, .addr_given = true, - .address = EM4X50_DEVICE_ID, + .addresses = (EM4X50_DEVICE_ID << 8) | EM4X50_DEVICE_ID, }; em4x50_word_t words[EM4X50_NO_WORDS]; - return (em4x50_read(&etd, words, false) == PM3_SUCCESS); + return (em4x50_read(&etd, words) == PM3_SUCCESS); } int read_em4x50_uid(void) { em4x50_data_t etd = { .pwd_given = false, .addr_given = true, - .address = EM4X50_DEVICE_SERIAL, + .addresses = (EM4X50_DEVICE_SERIAL << 8) | EM4X50_DEVICE_SERIAL, }; em4x50_word_t words[EM4X50_NO_WORDS]; - int res = em4x50_read(&etd, words, false); + int res = em4x50_read(&etd, words); if (res == PM3_SUCCESS) PrintAndLogEx(INFO, " Serial: " _GREEN_("%s"), sprint_hex(words[EM4X50_DEVICE_SERIAL].byte, 4)); return res; } -int CmdEM4x50Info(const char *Cmd) { - - // envoke reading of a EM4x50 tag which has to be on the antenna because - // decoding is done by the device (not on client side) - - bool errors = false, verbose = false; - uint8_t cmdp = 0; - em4x50_data_t etd; - etd.pwd_given = false; - - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - - case 'h': - return usage_lf_em4x50_info(); - - case 'p': - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - etd.pwd_given = true; - cmdp += 2; - break; - - case 'v': - verbose = true; - cmdp += 1; - break; - - default: - PrintAndLogEx(WARNING, " Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } - - // validation - if (errors) - return usage_lf_em4x50_info(); - - clearCommandBuffer(); - SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); - - PacketResponseNG resp; - if (!WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, TIMEOUT)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); - return PM3_ETIMEOUT; - } - - bool success = (resp.status & STATUS_SUCCESS) >> 1; - if (success) { - print_info_result(resp.data.asBytes, verbose); - return PM3_SUCCESS; - } - - PrintAndLogEx(FAILED, "reading tag " _RED_("failed")); - return PM3_ESOFT; -} - -int CmdEM4x50Write(const char *Cmd) { - - // envoke writing a single word (32 bit) to a EM4x50 tag - - em4x50_data_t etd = { .pwd_given = false }; - - bool errors = false, bword = false, baddr = false; - uint8_t cmdp = 0; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': { - return usage_lf_em4x50_write(); - } - case 'p': { - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - etd.pwd_given = true; - cmdp += 2; - break; - } - case 'w': { - if (param_gethex(Cmd, cmdp + 1, etd.word, 8)) { - PrintAndLogEx(FAILED, "\n word has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - bword = true; - cmdp += 2; - break; - } - case 'a': { - param_getdec(Cmd, cmdp + 1, &etd.address); - - // validation - if (etd.address < 1 || etd.address > 31) { - PrintAndLogEx(FAILED, "\n error, address has to be in range [1-31]\n"); - return PM3_EINVARG; - } - baddr = true; - cmdp += 2; - break; - } - default: { - PrintAndLogEx(WARNING, "\n Unknown parameter '%c'\n", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } - } - - if (errors || !bword || !baddr) - return usage_lf_em4x50_write(); - - clearCommandBuffer(); - SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); - PacketResponseNG resp; - if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); - return PM3_ETIMEOUT; - } - - if (resp.status == PM3_ETEAROFF) - return PM3_SUCCESS; - - bool isOK = (resp.status & STATUS_SUCCESS) >> 1; - if (isOK == false) { - PrintAndLogEx(FAILED, "writing " _RED_("failed")); - return PM3_ESOFT; - } - - if (etd.pwd_given) { - bool login = resp.status & STATUS_LOGIN; - if (login == false) { - PrintAndLogEx(FAILED, "login failed"); - return PM3_ESOFT; - } - PrintAndLogEx(SUCCESS, "login with password " _YELLOW_("%s"), sprint_hex_inrow(etd.password, 4)); - } - - // display result of writing operation in structured format - uint8_t *data = resp.data.asBytes; - em4x50_word_t words[EM4X50_NO_WORDS]; - - prepare_result(data, etd.address, etd.address, words); - print_result(words, etd.address, etd.address); - PrintAndLogEx(SUCCESS, "Successfully wrote to tag"); - PrintAndLogEx(HINT, "Try `" _YELLOW_("lf em 4x50_read a %u") "` - to read your data", etd.address); - return PM3_SUCCESS; -} - -static void print_write_password_result(PacketResponseNG *resp, const em4x50_data_t *etd) { - - // display result of password changing operation - - char string[NO_CHARS_MAX] = {0}, pstring[NO_CHARS_MAX] = {0}; - - sprintf(pstring, "\n writing new password " _GREEN_("ok")); - strcat(string, pstring); - - PrintAndLogEx(NORMAL, "%s\n", string); -} - -int CmdEM4x50WritePassword(const char *Cmd) { - - // envokes changing the password of EM4x50 tag - - bool errors = false, bpwd = false, bnpwd = false, success = false; - uint8_t cmdp = 0; - em4x50_data_t etd; - PacketResponseNG resp; - - // init - etd.pwd_given = false; - etd.newpwd_given = false; - - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_lf_em4x50_write_password(); - - case 'p': - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - bpwd = true; - etd.pwd_given = true; - cmdp += 2; - break; - - case 'n': - if (param_gethex(Cmd, cmdp + 1, etd.new_password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - bnpwd = true; - etd.newpwd_given = true; - cmdp += 2; - break; - - default: - PrintAndLogEx(WARNING, "\n Unknown parameter '%c'\n", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } - - if (errors || !bpwd || !bnpwd) - return usage_lf_em4x50_write_password(); - - clearCommandBuffer(); - SendCommandNG(CMD_LF_EM4X50_WRITE_PASSWORD, (uint8_t *)&etd, sizeof(etd)); - - if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE_PASSWORD, &resp, TIMEOUT)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); - return PM3_ETIMEOUT; - } - - if (resp.status == PM3_ETEAROFF) - return PM3_SUCCESS; - - success = (bool)resp.status; - - // get, prepare and print response - if (success) - print_write_password_result(&resp, &etd); - else - PrintAndLogEx(NORMAL, "\nwriting password " _RED_("failed") "\n"); - - return (success) ? PM3_SUCCESS : PM3_ESOFT; -} - -int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out, bool verbose) { +int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out) { // envoke reading - // - without option -> standard read mode - // - with given address (option a) (and optional password if address is + // - with given address (option b) (and optional password if address is // read protected) -> selective read mode em4x50_data_t edata = { .pwd_given = false, .addr_given = false }; @@ -532,145 +511,250 @@ int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out, bool verbose) { return PM3_ETIMEOUT; } - bool isOK = (resp.status & STATUS_SUCCESS) >> 1; - if (isOK == false) { - if (verbose) - PrintAndLogEx(FAILED, "reading " _RED_("failed")); - + if (resp.status != PM3_SUCCESS) return PM3_ESOFT; - } - - if (edata.pwd_given) { - bool login = resp.status & STATUS_LOGIN; - if (login == false) { - PrintAndLogEx(FAILED, "login failed"); - return PM3_ESOFT; - } - PrintAndLogEx(SUCCESS, "login with password " _YELLOW_("%s"), sprint_hex_inrow(etd->password, 4)); - } uint8_t *data = resp.data.asBytes; em4x50_word_t words[EM4X50_NO_WORDS]; - if (edata.addr_given) { - prepare_result(data, etd->address, etd->address, words); - } else { - int now = (resp.status & STATUS_NO_WORDS) >> 2; - prepare_result(data, 0, now - 1, words); - } + prepare_result(data, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF, words); - if (out != NULL) { + if (out != NULL) memcpy(out, &words, sizeof(em4x50_word_t) * EM4X50_NO_WORDS); - } - print_result(words, etd->address, etd->address); + print_result(words, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF); + return PM3_SUCCESS; } int CmdEM4x50Read(const char *Cmd) { + int pwdLen = 0; + int addr = 0; + uint8_t pwd[4] = {0x0}; em4x50_data_t etd; + + // init memset(&etd, 0x00, sizeof(em4x50_data_t)); - - etd.pwd_given = false; etd.addr_given = false; - etd.newpwd_given = false; + etd.pwd_given = false; - bool errors = false; - uint8_t cmdp = 0; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 read", + "Reads single EM4x50 block/word.", + "lf em 4x50 read -b 3 -> reads block 3\n" + "lf em 4x50 read -b 32 -p 12345678 -> reads block 32 with password 0x12345678\n" + ); - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': { - return usage_lf_em4x50_read(); - } - case 'a': { - param_getdec(Cmd, cmdp + 1, &etd.address); + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "block/word address"), + arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; - // validation - if (etd.address <= 0 || etd.address >= EM4X50_NO_WORDS) { - PrintAndLogEx(FAILED, "\n error, address has to be in range [1-33]\n"); - return PM3_EINVARG; - } - etd.addr_given = true; - cmdp += 2; - break; - } - case 'p': { - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - etd.pwd_given = true; - cmdp += 2; - break; - } - default: { - PrintAndLogEx(WARNING, "\n Unknown parameter '%c'\n", param_getchar(Cmd, cmdp)); - errors = true; - break; - } + CLIExecWithReturn(ctx, Cmd, argtable, true); + + addr = arg_get_int_def(ctx, 1, 0); + CLIGetHexWithReturn(ctx, 2, pwd, &pwdLen); + + if (addr <= 0 || addr >= EM4X50_NO_WORDS) { + return PM3_EINVARG; + } else { + etd.addresses = (addr << 8) | addr; + etd.addr_given = true; + } + + if (pwdLen) { + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; } } - if (errors || strlen(Cmd) == 0 || etd.addr_given == false) - return usage_lf_em4x50_read(); + CLIParserFree(ctx); - return em4x50_read(&etd, NULL, true); + return em4x50_read(&etd, NULL); +} + +int CmdEM4x50Info(const char *Cmd) { + + // envoke reading of a EM4x50 tag which has to be on the antenna because + // decoding is done by the device (not on client side) + + int pwdLen = 0; + int status = 0; + uint8_t pwd[4] = {0x0}; + em4x50_data_t etd = {.pwd_given = false}; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 info", + "Tag information EM4x50.", + "lf em 4x50 info\n" + "lf em 4x50 info -p 12345678 -> uses password 0x12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd, &pwdLen); + + if (pwdLen) { + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + } + } + + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + status = resp.status; + + if (status == PM3_SUCCESS) + print_info_result(resp.data.asBytes); + else + PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); + + return status; +} + +int CmdEM4x50Reader(const char *Cmd) { + + int now = 0; + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 reader", + "Shows standard read data of EM4x50 tag.", + "lf em 4x50 reader\n" + "lf em 4x50 reader -@ -> continuous reader mode" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0("@", NULL, "optional - continuous reader mode"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool cm = arg_get_lit(ctx, 1); + CLIParserFree(ctx); + + // start + do { + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_READER, 0, 0); + WaitForResponseTimeoutW(CMD_LF_EM4X50_READER, &resp, -1, false); + + now = resp.status; + + // print response + if (now > 0) { + + em4x50_word_t words[EM4X50_NO_WORDS]; + prepare_result(resp.data.asBytes, 0, now - 1, words); + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, " word (msb) | word (lsb) "); + PrintAndLogEx(INFO, "-------------+-------------"); + + for (int i = 0; i < now; i++) { + + char r[30] = {0}; + for (int j = 3; j >= 0; j--) + sprintf(r + strlen(r), "%02x ", reflect8(words[i].byte[j])); + + PrintAndLogEx(INFO, _GREEN_(" %s") "| %s", + sprint_hex(words[i].byte, 4), + r + ); + } + + PrintAndLogEx(INFO, "-------------+-------------"); + } + } while (cm && !kbd_enter_pressed()); + + return PM3_SUCCESS; } int CmdEM4x50Dump(const char *Cmd) { - em4x50_data_t etd; - etd.pwd_given = false; - etd.addr_given = false; - etd.newpwd_given = false; - - char filename[FILE_PATH_SIZE] = {0x00}; + int fnLen = 0, pwdLen = 0, status = 0; + uint8_t pwd[4] = {0x0}; + char filename[FILE_PATH_SIZE] = {0}; char *fptr = filename; + em4x50_data_t etd = {.pwd_given = false}; + uint8_t data[DUMP_FILESIZE] = {0}; - bool errors = false; - uint8_t cmdp = 0; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_lf_em4x50_dump(); - break; - case 'f': - param_getstr(Cmd, cmdp + 1, filename, FILE_PATH_SIZE); - cmdp += 2; - break; - case 'p': { - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\n password has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - etd.pwd_given = true; - cmdp += 2; - break; - } - default: - PrintAndLogEx(WARNING, " Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - }; + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 dump", + "Reads all blocks/words from EM4x50 tag and saves dump in bin/eml/json format.", + "lf em 4x50 dump -> saves dump in lf-4x50--dump.bin/eml/json\n" + "lf em 4x50 dump -f mydump.eml -> saves dump in mydump.eml\n" + "lf em 4x50 dump -p 12345678\n" + "lf em 4x50 dump -f mydump.eml -p 12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), + arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIParamStrToBuf(arg_get_str(ctx, 1), + (uint8_t *)filename, + FILE_PATH_SIZE, + &fnLen + ); + CLIGetHexWithReturn(ctx, 2, pwd, &pwdLen); + + if (pwdLen) { + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + } } + + CLIParserFree(ctx); - // validation - if (errors) - return usage_lf_em4x50_dump(); - - PrintAndLogEx(INFO, "reading EM4x50 tag"); + PrintAndLogEx(INFO, "Reading EM4x50 tag"); clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, TIMEOUT)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } - bool success = (resp.status & STATUS_SUCCESS) >> 1; - if (success == false) { - PrintAndLogEx(FAILED, "reading tag " _RED_("failed")); + status = resp.status; + if (status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); return PM3_ESOFT; } @@ -678,26 +762,179 @@ int CmdEM4x50Dump(const char *Cmd) { em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(resp.data.asBytes, 0, EM4X50_NO_WORDS - 1, words); + // result output PrintAndLogEx(INFO, _YELLOW_("EM4x50 data:")); print_result(words, 0, EM4X50_NO_WORDS - 1); // user supplied filename? - if (strlen(filename) == 0) { + if (fnLen == 0) { PrintAndLogEx(INFO, "Using UID as filename"); fptr += sprintf(fptr, "lf-4x50-"); - FillFileNameByUID(fptr, words[EM4X50_DEVICE_SERIAL].byte, "-dump", 4); + FillFileNameByUID(fptr, words[EM4X50_DEVICE_ID].byte, "-dump", 4); } - uint8_t data[EM4X50_NO_WORDS * 4] = {0}; - for (int i = 0; i < EM4X50_NO_WORDS; i++) { + for (int i = 0; i < EM4X50_NO_WORDS; i++) memcpy(data + (i * 4), words[i].byte, 4); - } // saveFileEML will add .eml extension to filename // saveFile (binary) passes in the .bin extension. + // saveFileJSON adds .json extension saveFileEML(filename, data, sizeof(data), 4); saveFile(filename, ".bin", data, sizeof(data)); - //saveFileJSON... + saveFileJSON(filename, jsfEM4x50, data, sizeof(data), NULL); + + return PM3_SUCCESS; +} + +int CmdEM4x50Write(const char *Cmd) { + + // envoke writing a single word (32 bit) to a EM4x50 tag + + int wordLen = 0, pwdLen = 0, addr = 0; + int status = 0; + uint8_t word[4] = {0x0}; + uint8_t pwd[4] = {0x0}; + em4x50_data_t etd = {.pwd_given = false}; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 write", + "Writes single block/word to EM4x50 tag.", + "lf em 4x50 write -b 3 -d 4f22e7ff -> writes 0x4f22e7ff to block 3\n" + "lf em 4x50 write -b 3 -d 4f22e7ff -p 12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "block/word address, dec"), + arg_str1("d", "data", "", "data, 4 bytes, lsb"), + arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + addr = arg_get_int_def(ctx, 1, 0); + CLIGetHexWithReturn(ctx, 2, word, &wordLen); + CLIGetHexWithReturn(ctx, 3, pwd, &pwdLen); + + if (addr <= 0 || addr >= EM4X50_NO_WORDS) { + PrintAndLogEx(FAILED, "address has to be within range [0, 31]"); + return PM3_EINVARG; + } else { + etd.addresses = (addr << 8) | addr; + etd.addr_given = true; + } + if (wordLen != 4) { + PrintAndLogEx(FAILED, "word/data length must be 4 bytes instead of %d", wordLen); + return PM3_EINVARG; + } else { + etd.word = BYTES2UINT32(word); + } + if (pwdLen) { + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + } + } + + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + status = resp.status; + if (status == PM3_ETEAROFF) + return PM3_SUCCESS; + + if (status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Writing " _RED_("failed")); + return PM3_ESOFT; + } + + // display result of writing operation in structured format + uint8_t *data = resp.data.asBytes; + em4x50_word_t words[EM4X50_NO_WORDS]; + + prepare_result(data, addr, addr, words); + print_result(words, addr, addr); + PrintAndLogEx(SUCCESS, "Successfully wrote to tag"); + PrintAndLogEx(HINT, "Try `" _YELLOW_("lf em 4x50_read a %u") "` - to read your data", addr); + + return PM3_SUCCESS; +} + +int CmdEM4x50WritePwd(const char *Cmd) { + + // envokes changing the password of EM4x50 tag + + int status = PM3_EFAILED; + int pwdLen = 0, npwdLen = 0; + uint8_t pwd[4] = {0x0}, npwd[4] = {0x0}; + PacketResponseNG resp; + em4x50_data_t etd; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 writepwd", + "Writes EM4x50 password.", + "lf em 4x50 writepwd -p 4f22e7ff -n 12345678 -> replaces password 0x4f22e7ff with 0x12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("p", "pwd", "", "password, 4 bytes, lsb"), + arg_str1("n", "newpwd", "", "new password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd, &pwdLen); + CLIGetHexWithReturn(ctx, 2, npwd, &npwdLen); + + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + } + if (npwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", npwdLen); + return PM3_EINVARG; + } else { + etd.password2 = BYTES2UINT32(npwd); + } + + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_WRITEPWD, (uint8_t *)&etd, sizeof(etd)); + + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITEPWD, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + status = resp.status; + + if (status == PM3_ETEAROFF) + return PM3_SUCCESS; + + // print response + if (status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Writing password " _RED_("failed")); + return PM3_EFAILED; + } + + PrintAndLogEx(SUCCESS, "Writing new password " _GREEN_("ok")); + return PM3_SUCCESS; } @@ -705,67 +942,275 @@ int CmdEM4x50Wipe(const char *Cmd) { // fills EM4x50 tag with zeros including password - bool errors = false, bpwd = false; - uint8_t cmdp = 0; - em4x50_data_t etd; + bool isOK = false; + int pwdLen = 0; + uint8_t pwd[4] = {0x0}; + em4x50_data_t etd = {.pwd_given = false, .word = 0x0, .password2 = 0x0}; PacketResponseNG resp; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 wipe", + "Wipes EM4x50 tag.", + "lf em 4x50 wipe -p 12345678 -> wipes tag with password 0x12345678\n" + ); - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_lf_em4x50_wipe(); + void *argtable[] = { + arg_param_begin, + arg_str1("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; - case 'p': - if (param_gethex(Cmd, cmdp + 1, etd.password, 8)) { - PrintAndLogEx(FAILED, "\npassword has to be 8 hex symbols\n"); - return PM3_EINVARG; - } - bpwd = true; - cmdp += 2; - break; - - default: - PrintAndLogEx(WARNING, "\nUnknown parameter '%c'\n", param_getchar(Cmd, cmdp)); - errors = true; - break; - } + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd, &pwdLen); + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; } - if (errors || !bpwd) - return usage_lf_em4x50_wipe(); - + CLIParserFree(ctx); + + // clear password clearCommandBuffer(); - SendCommandNG(CMD_LF_EM4X50_WIPE, (uint8_t *)&etd, sizeof(etd)); - - if (!WaitForResponseTimeout(CMD_LF_EM4X50_WIPE, &resp, 2 * TIMEOUT)) { - PrintAndLogEx(WARNING, "\ntimeout while waiting for reply.\n"); + SendCommandNG(CMD_LF_EM4X50_WRITEPWD, (uint8_t *)&etd, sizeof(etd)); + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITEPWD, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } - // print response - bool isOK = resp.status; - if (isOK) { - PrintAndLogEx(SUCCESS, "\nwiping data " _GREEN_("ok") "\n"); + if (resp.status == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Resetting password " _GREEN_("ok")); } else { - PrintAndLogEx(FAILED, "\nwiping data " _RED_("failed") "\n"); + PrintAndLogEx(FAILED, "Resetting password " _RED_("failed")); return PM3_ESOFT; } + // from now on new password 0x0 + etd.password1 = 0x0; + + // clear data (words 1 to 31) + for (int i = 1; i < EM4X50_DEVICE_SERIAL; i++) { + + // no login necessary for blocks 3 to 31 + etd.pwd_given = (i <= EM4X50_CONTROL); + + PrintAndLogEx(INPLACE, "Wiping block %i", i); + + etd.addresses = i << 8 | i; + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + isOK = resp.status; + if (isOK != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "Wiping data " _RED_("failed")); + return PM3_ESOFT; + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Wiping data " _GREEN_("ok")); + + PrintAndLogEx(INFO, "Done"); + return PM3_SUCCESS; } +int CmdEM4x50Restore(const char *Cmd) { + + int uidLen = 0, fnLen = 0, pwdLen = 0, status = 0; + int startblock = EM4X50_CONTROL + 1; + uint8_t pwd[4] = {0x0}, uid[4] = {0x0}; + uint8_t data[DUMP_FILESIZE] = {0x0}; + size_t bytes_read = 0; + char filename[FILE_PATH_SIZE] = {0}; + char *fptr = filename; + em4x50_data_t etd = {.pwd_given = false}; + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 restore", + "Restores data from dumpfile onto a Em4x50 tag.", + "lf em 4x50 restore -u 1b5aff5c -> uses lf-4x50-1B5AFF5C-dump.bin\n" + "lf em 4x50 restore -f mydump.eml -> uses mydump.eml\n" + "lf em 4x50 restore -u 1b5aff5c -p 12345678\n" + "lf em 4x50 restore -f mydump.eml -p 12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("u", "uid", "", "uid, 4 bytes, msb, restore from lf-4x50--dump.bin"), + arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), + arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, uid, &uidLen); + CLIParamStrToBuf(arg_get_str(ctx, 2), + (uint8_t *)filename, + FILE_PATH_SIZE, + &fnLen + ); + CLIGetHexWithReturn(ctx, 3, pwd, &pwdLen); + + if ((uidLen && fnLen) || (!uidLen && !fnLen)) { + PrintAndLogEx(FAILED, "either use option 'u' or option 'f'"); + return PM3_EINVARG; + } + + if (uidLen) { + PrintAndLogEx(INFO, "Using UID as filename"); + fptr += sprintf(fptr, "lf-4x50-"); + FillFileNameByUID(fptr, uid, "-dump", 4); + } + + if (pwdLen) { + if (pwdLen != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwdLen); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + // if password is available protection and control word can be restored + startblock = EM4X50_PROTECTION; + } + } + + CLIParserFree(ctx); + + PrintAndLogEx(INFO, "Restoring " _YELLOW_("%s")" to card", filename); + + // read data from dump file; file type has to be "bin", "eml" or "json" + if (em4x50_load_file(filename, data, DUMP_FILESIZE, &bytes_read) != PM3_SUCCESS) + return PM3_EFILE; + + for (int i = startblock; i < EM4X50_DEVICE_SERIAL; i++) { + + PrintAndLogEx(INPLACE, "Restoring block %i", i); + + etd.addresses = i << 8 | i; + etd.word = reflect32(BYTES2UINT32((data + 4 * i))); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + status = resp.status; + if (status != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "Restoring data " _RED_("failed")); + return PM3_ESOFT; + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Restoring data " _GREEN_("ok")); + + PrintAndLogEx(INFO, "Done"); + + return PM3_SUCCESS; +} + +int CmdEM4x50Sim(const char *Cmd) { + + int slen = 0, status = 0; + size_t bytes_read = 0; + uint8_t data[DUMP_FILESIZE] = {0x0}; + uint8_t destfn[32] = {0}; + + char filename[FILE_PATH_SIZE] = {0}; + PacketResponseNG resp; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 sim", + "Simulates a EM4x50 tag", + "lf em 4x50 sim -> simulates EM4x50 data in memory (upload via em4x50_eload).\n" + "lf em 4x50 sim -f mydump.eml -> simulates content of file ./mydump\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "dump filename, bin/eml/json"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &slen); + CLIParserFree(ctx); + + // read data from dump file; file type has to be "bin", "eml" or "json" + if (slen != 0) { + + // load file content + if (em4x50_load_file(filename, data, DUMP_FILESIZE, &bytes_read) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Read error"); + return PM3_EFILE; + } + + PrintAndLogEx(INFO, "Uploading dump " _YELLOW_("%s") " to davice", filename); + + // upload to device + if (IfPm3Flash()) { + sprintf((char *)destfn, "em4x50_sim.bin"); + status = flashmem_spiffs_load(destfn, data, DUMP_FILESIZE); + if (status != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "SPIFFS upload failed"); + return status; + } + } else { + em4x50_seteml(data, 0, DUMP_FILESIZE); + } + + PrintAndLogEx(INFO, "Simulating data from " _YELLOW_("%s"), filename); + } else { + PrintAndLogEx(INFO, "Simulating data from emulator memory"); + } + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_SIM, destfn, sizeof(destfn)); + WaitForResponse(CMD_LF_EM4X50_SIM, &resp); + + status = resp.status; + if (status == PM3_SUCCESS) + PrintAndLogEx(INFO, "Done"); + else + PrintAndLogEx(FAILED, "No valid em4x50 data in memory."); + + return resp.status; +} + static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - {"dump", CmdEM4x50Dump, IfPm3EM4x50, "dump EM4x50 tag"}, - {"info", CmdEM4x50Info, IfPm3EM4x50, "tag information EM4x50"}, - {"write", CmdEM4x50Write, IfPm3EM4x50, "write word data to EM4x50"}, - {"write_password", CmdEM4x50WritePassword, IfPm3EM4x50, "change password of EM4x50 tag"}, - {"read", CmdEM4x50Read, IfPm3EM4x50, "read word data from EM4x50"}, - {"wipe", CmdEM4x50Wipe, IfPm3EM4x50, "wipe data from EM4x50"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"dump", CmdEM4x50Dump, IfPm3EM4x50, "dump EM4x50 tag"}, + {"info", CmdEM4x50Info, IfPm3EM4x50, "tag information EM4x50"}, + {"write", CmdEM4x50Write, IfPm3EM4x50, "write word data to EM4x50"}, + {"writepwd",CmdEM4x50WritePwd, IfPm3EM4x50, "change password of EM4x50"}, + {"read", CmdEM4x50Read, IfPm3EM4x50, "read word data from EM4x50"}, + {"wipe", CmdEM4x50Wipe, IfPm3EM4x50, "wipe EM4x50 tag"}, + {"brute", CmdEM4x50Brute, IfPm3EM4x50, "guess password of EM4x50"}, + {"login", CmdEM4x50Login, IfPm3EM4x50, "login into EM4x50"}, + {"restore",CmdEM4x50Restore, IfPm3EM4x50, "restore EM4x50 dump to tag"}, + {"sim", CmdEM4x50Sim, IfPm3EM4x50, "simulate EM4x50 tag"}, + {"reader", CmdEM4x50Reader, IfPm3EM4x50, "show standard read mode data of EM4x50"}, + {"eload", CmdEM4x50ELoad, IfPm3EM4x50, "upload dump of EM4x50 to flash memory"}, + {"esave", CmdEM4x50ESave, IfPm3EM4x50, "save flash memory to file"}, + {"chk", CmdEM4x50Chk, IfPm3EM4x50, "check passwords from dictionary"}, {NULL, NULL, NULL, NULL} }; + static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far CmdsHelp(CommandTable); diff --git a/client/src/cmdlfem4x50.h b/client/src/cmdlfem4x50.h index 36e87ce70..dab84ba6c 100644 --- a/client/src/cmdlfem4x50.h +++ b/client/src/cmdlfem4x50.h @@ -11,20 +11,27 @@ #ifndef CMDLFEM4X50_H__ #define CMDLFEM4X50_H__ -#include"common.h" #include "em4x50.h" int CmdLFEM4X50(const char *Cmd); int read_em4x50_uid(void); bool detect_4x50_block(void); -int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out, bool verbose); +int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out); int CmdEM4x50Info(const char *Cmd); int CmdEM4x50Write(const char *Cmd); -int CmdEM4x50WritePassword(const char *Cmd); +int CmdEM4x50WritePwd(const char *Cmd); int CmdEM4x50Read(const char *Cmd); int CmdEM4x50Dump(const char *Cmd); int CmdEM4x50Wipe(const char *Cmd); +int CmdEM4x50Brute(const char *Cmd); +int CmdEM4x50Login(const char *Cmd); +int CmdEM4x50Restore(const char *Cmd); +int CmdEM4x50Sim(const char *Cmd); +int CmdEM4x50Reader(const char *Cmd); +int CmdEM4x50ELoad(const char *Cmd); +int CmdEM4x50ESave(const char *Cmd); +int CmdEM4x50Chk(const char *Cmd); #endif diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 1b47a19d9..f7ccf1516 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -521,6 +521,20 @@ int saveFileJSONex(const char *preferredName, JSONFileType ftype, uint8_t *data, } break; } + case jsfEM4x50: { + JsonSaveStr(root, "FileType", "EM4X50"); + JsonSaveBufAsHexCompact(root, "$.Card.Protection", data + (1 * 4), 4); + JsonSaveBufAsHexCompact(root, "$.Card.Config", data + (2 * 4), 4); + JsonSaveBufAsHexCompact(root, "$.Card.Serial", data + (32 * 4), 4); + JsonSaveBufAsHexCompact(root, "$.Card.UID", data + (33 * 4), 4); + + for (size_t i = 0; i < (datalen / 4); i++) { + char path[PATH_MAX_LENGTH] = {0}; + sprintf(path, "$.blocks.%zu", i); + JsonSaveBufAsHexCompact(root, path, data + (i * 4), 4); + } + break; + } case jsfMfPlusKeys: { JsonSaveStr(root, "FileType", "mfp"); JsonSaveBufAsHexCompact(root, "$.Card.UID", &data[0], 7); @@ -1139,6 +1153,27 @@ int loadFileJSONex(const char *preferredName, void *data, size_t maxdatalen, siz *datalen = sptr; } + if (!strcmp(ctype, "EM4X50")) { + size_t sptr = 0; + for (size_t i = 0; i < (maxdatalen / 4); i++) { + if (sptr + 4 > maxdatalen) { + retval = PM3_EMALLOC; + goto out; + } + + char blocks[30] = {0}; + sprintf(blocks, "$.blocks.%zu", i); + + size_t len = 0; + JsonLoadBufAsHex(root, blocks, &udata[sptr], 4, &len); + if (!len) + break; + + sptr += len; + } + *datalen = sptr; + } + out: if (callback != NULL) { diff --git a/client/src/fileutils.h b/client/src/fileutils.h index 49992c998..e307a2492 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -66,6 +66,7 @@ typedef enum { jsfMfDesfireKeys, jsfEM4x05, jsfEM4x69, + jsfEM4x50, } JSONFileType; typedef enum { diff --git a/client/src/scripting.c b/client/src/scripting.c index faf8e3e76..6ac012199 100644 --- a/client/src/scripting.c +++ b/client/src/scripting.c @@ -1146,8 +1146,7 @@ static int l_em4x50_read(lua_State *L) { em4x50_data_t etd; memset(&etd, 0x00, sizeof(em4x50_data_t)); etd.addr_given = true; - etd.address = addr & 0xFF; - etd.newpwd_given = false; + etd.addresses = addr & 0xFF; // get password const char *p_pwd = luaL_checkstring(L, 2); @@ -1162,31 +1161,29 @@ static int l_em4x50_read(lua_State *L) { PrintAndLogEx(DEBUG, " Pwd %08X", pwd); - etd.password[0] = pwd & 0xFF; - etd.password[1] = (pwd >> 8) & 0xFF; - etd.password[2] = (pwd >> 16) & 0xFF; - etd.password[3] = (pwd >> 24) & 0xFF; + etd.password1 = pwd; etd.pwd_given = true; } - PrintAndLogEx(DEBUG, "Addr %u", etd.address); + PrintAndLogEx(DEBUG, "Addr %u", etd.addresses & 0xFF); if (etd.pwd_given) - PrintAndLogEx(DEBUG, " Pwd %s", sprint_hex(etd.password, sizeof(etd.password))); + PrintAndLogEx(DEBUG, " Pwd %08x", etd.password1); em4x50_word_t words[EM4X50_NO_WORDS]; - int res = em4x50_read(&etd, words, false); + int res = em4x50_read(&etd, words); if (res != PM3_SUCCESS) { return returnToLuaWithError(L, "Failed to read EM4x50 data"); } uint32_t word = ( - words[etd.address].byte[0] << 24 | - words[etd.address].byte[1] << 16 | - words[etd.address].byte[2] << 8 | - words[etd.address].byte[3] + words[etd.addresses & 0xFF].byte[0] << 24 | + words[etd.addresses & 0xFF].byte[1] << 16 | + words[etd.addresses & 0xFF].byte[2] << 8 | + words[etd.addresses & 0xFF].byte[3] ); lua_pushinteger(L, word); + return 1; } diff --git a/common/commonutil.c b/common/commonutil.c index 02f64a69e..70114bba1 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -97,6 +97,22 @@ uint16_t reflect16(uint16_t b) { return v; } +uint32_t reflect32(uint32_t b) { + // https://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable + uint32_t v = b; // 32-bit word to reverse bit order + // swap odd and even bits + v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); + // swap consecutive pairs + v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); + // swap nibbles ... + v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); + // swap bytes + v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); + // swap 2-byte long pairs + v = ( v >> 16 ) | ( v << 16); + return v; +} + void num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { while (len--) { dest[len] = (uint8_t) n; diff --git a/common/commonutil.h b/common/commonutil.h index 6bf330e7c..4ba65d171 100644 --- a/common/commonutil.h +++ b/common/commonutil.h @@ -47,6 +47,7 @@ void FormatVersionInformation(char *dst, int len, const char *prefix, void *vers uint32_t reflect(uint32_t v, int b); // used in crc.c ... uint8_t reflect8(uint8_t b); // dedicated 8bit reversal uint16_t reflect16(uint16_t b); // dedicated 16bit reversal +uint32_t reflect32(uint32_t b); // dedicated 32bit reversal void num_to_bytes(uint64_t n, size_t len, uint8_t *dest); uint64_t bytes_to_num(uint8_t *src, size_t len); diff --git a/include/em4x50.h b/include/em4x50.h index 4542e7bec..573c58375 100644 --- a/include/em4x50.h +++ b/include/em4x50.h @@ -34,32 +34,20 @@ #define LAST_WORD_WRITE_INHIBITED 3 // fourth byte // misc -#define STATUS_NO_WORDS 0xfc -#define STATUS_SUCCESS 0x2 -#define STATUS_LOGIN 0x1 -#define NO_CHARS_MAX 400 #define TIMEOUT 2000 +#define DUMP_FILESIZE 136 typedef struct { bool addr_given; bool pwd_given; - bool newpwd_given; - uint8_t password[4]; - uint8_t new_password[4]; - uint8_t addresses[4]; - uint8_t address; - uint8_t word[4]; + uint32_t password1; + uint32_t password2; + uint32_t word; + uint32_t addresses; } PACKED em4x50_data_t; typedef struct { uint8_t byte[4]; - uint8_t row_parity[4]; - uint8_t col_parity; - uint8_t stopbit; - bool rparity[4]; - bool cparity[8]; - bool stopparity; - bool parity; } PACKED em4x50_word_t; #endif /* EM4X50_H__ */ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 6acc1c1c5..bce9b57d7 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -507,9 +507,14 @@ typedef struct { #define CMD_LF_EM410X_WATCH 0x021C #define CMD_LF_EM4X50_INFO 0x0240 #define CMD_LF_EM4X50_WRITE 0x0241 -#define CMD_LF_EM4X50_WRITE_PASSWORD 0x0242 +#define CMD_LF_EM4X50_WRITEPWD 0x0242 #define CMD_LF_EM4X50_READ 0x0243 -#define CMD_LF_EM4X50_WIPE 0x0244 +#define CMD_LF_EM4X50_BRUTE 0x0245 +#define CMD_LF_EM4X50_LOGIN 0x0246 +#define CMD_LF_EM4X50_SIM 0x0250 +#define CMD_LF_EM4X50_READER 0x0251 +#define CMD_LF_EM4X50_ESET 0x0252 +#define CMD_LF_EM4X50_CHK 0x0253 // Sampling configuration for LF reader/sniffer #define CMD_LF_SAMPLING_SET_CONFIG 0x021D #define CMD_LF_FSK_SIMULATE 0x021E