diff --git a/CHANGELOG.md b/CHANGELOG.md index de0373163..6c6cba7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,26 @@ 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] - - Fix `hf 15 sim` - Added basic response to GET_SYSTEM_INFO and READBLOCK requests in order to fix iso15693 tag sim + - Fix issue #844 - `lf t55xx config` => recompute block0 (@cyberpunk-re) + - 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 (@cyberpunk-re) - 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) - Fix 'hf iclass wrbl' - dealing with tags in unsecured vs secured pagemode now is correct (@iceman1001) diff --git a/armsrc/Makefile b/armsrc/Makefile index f3caf6e22..46ba4f027 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -69,6 +69,12 @@ else SRC_EM4x50 = endif +ifneq (,$(findstring WITH_EM4x70,$(APP_CFLAGS))) + SRC_EM4x70 = em4x70.c +else + SRC_EM4x70 = +endif + ifneq (,$(findstring WITH_LCD,$(APP_CFLAGS))) SRC_LCD = fonts.c LCD.c else @@ -106,6 +112,7 @@ THUMBSRC = start.c \ $(SRC_FPC) \ $(SRC_HITAG) \ $(SRC_EM4x50) \ + $(SRC_EM4x70) \ $(SRC_SPIFFS) \ $(SRC_ISO14443a) \ $(SRC_ISO14443b) \ diff --git a/armsrc/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..c4cc6aaf2 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -31,6 +31,7 @@ #include "hitag2.h" #include "hitagS.h" #include "em4x50.h" +#include "em4x70.h" #include "iclass.h" #include "legicrfsim.h" //#include "cryptorfsim.h" @@ -479,6 +480,12 @@ static void SendCapabilities(void) { #else capabilities.compiled_with_em4x50 = false; #endif +#ifdef WITH_EM4x70 + capabilities.compiled_with_em4x70 = true; +#else + capabilities.compiled_with_em4x70 = false; +#endif + #ifdef WITH_HFSNIFF capabilities.compiled_with_hfsniff = true; #else @@ -1106,16 +1113,61 @@ 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 + +#ifdef WITH_EM4x70 + case CMD_LF_EM4X70_INFO: { + em4x70_info((em4x70_data_t *)packet->data.asBytes); break; } #endif diff --git a/armsrc/em4x50.c b/armsrc/em4x50.c index 27f5a08d9..36923b6a5 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,83 @@ 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; - -// auxiliary functions - -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; +// do nothing for using timer0 +static void wait_timer(uint32_t period) { + 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 +// extract and check parities +// return result of parity check and extracted plain data +static bool extract_parities(uint64_t word, uint32_t *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 +139,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,35 +160,49 @@ 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; +} + +// calculate signal properties (mean amplitudes) from measured data: +// 32 amplitudes (maximum values) -> mean amplitude value -> gHigh -> gLow static bool get_signalproperties(void) { - // calculate signal properties (mean amplitudes) from measured data: - // 32 amplitudes (maximum values) -> mean amplitude value -> gHigh -> gLow - bool signal_found = false; int no_periods = 32, pct = 75, noise = 140; uint8_t sample_ref = 127; 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 +211,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,87 +227,80 @@ 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) { - - // 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 - // "find_double_listen_window" and "check_ack" +// 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" +static bool invalid_bit(void) { // 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; } +// check if pulse length corresponds to given length 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) { - - // send single bit according to EM4x50 application note and datasheet - +// send single bit according to EM4x50 application note and datasheet +static void em4x50_reader_send_bit(int bit) { // reset clock for the next bit AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; 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,49 +322,48 @@ static void em4x50_send_bit(int bit) { } } -static void em4x50_send_byte(uint8_t byte) { - - // send byte (without parity) - - for (int i = 0; i < 8; i++) - em4x50_send_bit((byte >> (7 - i)) & 1); - +// send byte (without parity) +static void em4x50_reader_send_byte(uint8_t byte) { + for (int i = 0; i < 8; i++) { + em4x50_reader_send_bit((byte >> (7 - i)) & 1); + } } -static void em4x50_send_byte_with_parity(uint8_t byte) { - - // send byte followed by its (equal) parity bit - +// send byte followed by its (equal) parity bit +static void em4x50_reader_send_byte_with_parity(uint8_t byte) { int parity = 0, bit = 0; 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]) { - - // send 32 bit word with parity bits according to EM4x50 datasheet - - for (int i = 0; i < 4; i++) - em4x50_send_byte_with_parity(bytes[i]); - +// send 32 bit word with parity bits according to EM4x50 datasheet +// word hast be sent in msb notation +static void em4x50_reader_send_word(const uint32_t word) { + 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); } +// find single listen window 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,28 +373,33 @@ 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) { - - // find two successive listen windows that indicate the beginning of - // data transmission - // double listen window to be detected within 1600 pulses -> worst case - // reason: first detectable double listen window after 34 words - // -> 34 words + 34 single listen windows -> about 1600 pulses - +// find two successive listen windows that indicate the beginning of +// data transmission +// double listen window to be detected within 1600 pulses -> worst case +// reason: first detectable double listen window after 34 words +// -> 34 words + 34 single listen windows -> about 1600 pulses +static int find_double_listen_window(bool bcommand) { 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,64 +420,67 @@ 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; } +// function is used to check wether a tag on the proxmark is an +// EM4x50 tag or not -> speed up "lf search" process static bool find_em4x50_tag(void) { - - // function is used to check wether a tag on the proxmark is an - // EM4x50 tag or not -> speed up "lf search" process return find_single_listen_window(); } -static bool 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); +// 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. +static int request_receive_mode(void) { + return find_double_listen_window(true); } +// returns true if signal structue corresponds to ACK, anything else is +// counted as NAK (-> false) +// Only relevant for pasword writing function: +// If is true then within the single listen window right after the +// ack signal a RM request has to be sent. static bool check_ack(bool bliw) { - - // returns true if signal structue corresponds to ACK, anything else is - // counted as NAK (-> false) - // Only relevant for pasword writing function: - // If is true then within the single listen window right after the - // ack signal a RM request has to be sent. - 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 +499,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 +523,565 @@ static bool check_ack(bool bliw) { return false; } -static int get_word_from_bitstream(uint8_t bits[EM4X50_TAG_WORD]) { - - // 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; +// 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 +static int get_word_from_bitstream(uint32_t *data) { + 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; - // simple login to EM4x50, - // used in operations that require authentication + for (int t = 0; t < EM4X50_T_TAG_FULL_PERIOD; t++) { - if (request_receive_mode()) { + // 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) == false) + return false;; + + if (em4x50_sim_send_bit(parity) == false) + 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) == false) { + 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) == false) + return false; + + // stop bit + if (em4x50_sim_send_bit(0) == false) + 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; +} + +// simple login to EM4x50, +// used in operations that require authentication +static bool login(uint32_t password) { + 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 -//============================================================================== +// searching for password in given range +static bool brute(uint32_t start, uint32_t stop, uint32_t *pwd) { + bool pwd_found = false; + int cnt = 0; -static bool reset(void) { + for (*pwd = start; *pwd <= stop; (*pwd)++) { - // resets EM4x50 tag (used by write function) + if (login(*pwd) == PM3_SUCCESS) { + + pwd_found = true; - if (request_receive_mode()) { + // 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) { - // send login command - em4x50_send_byte_with_parity(EM4X50_COMMAND_RESET); + // 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; +} + +// login into EM4x50 +void em4x50_login(uint32_t *password) { + em4x50_setup_read(); + + uint8_t status = PM3_EFAILED; + if (get_signalproperties() && find_em4x50_tag()) + status = login(*password); + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_LOGIN, status, NULL, 0); +} + +// envoke password search +void em4x50_brute(em4x50_data_t *etd) { + em4x50_setup_read(); + + bool bsuccess = false; + uint32_t pwd = 0x0; + if (get_signalproperties() && find_em4x50_tag()) + bsuccess = brute(etd->password1, etd->password2, &pwd); + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_BRUTE, bsuccess ? PM3_SUCCESS : PM3_EFAILED, (uint8_t *)(&pwd), sizeof(pwd)); +} + +// check passwords from dictionary content in flash memory +void em4x50_chk(uint8_t *filename) { + 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, sizeof(pwd)); +} + +// resets EM4x50 tag (used by write function) +static int reset(void) { + if (request_receive_mode() == PM3_SUCCESS) { + + // 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 -//============================================================================== +// reads data that tag transmits when exposed to reader field +// (standard read mode); number of read words is saved in +static int standard_read(int *now, uint32_t *words) { -static bool standard_read(int *now) { - - // 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]) { +// reads from "first word read" (fwr) to "last word read" (lwr) +// result is verified by "standard read mode" +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]) - // result is verified by "standard read mode" + 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 - int fwr = addresses[3]; // first word read - int lwr = addresses[2]; // last word read - 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; } +// reads by using "selective read mode" -> bidirectional communication +void em4x50_read(em4x50_data_t *etd) { + 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(); + + // iceman: this hardcoded 136 value.... + reply_ng(CMD_LF_EM4X50_READ, status, (uint8_t *)words, 136); +} + +// collects as much information as possible via selective read mode 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 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}; - 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 - - 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); + + // iceman: this hardcoded 136 value.... + reply_ng(CMD_LF_EM4X50_INFO, status, (uint8_t *)words, 136); } -void em4x50_read(em4x50_data_t *etd) { +// reads data that tag transmits "voluntarily" -> standard read mode +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") - - 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) { - - // writes to specified
- - if (request_receive_mode()) { +// writes to specified +static int write(uint32_t word, uint32_t addresses) { + + 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 +1090,25 @@ 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]) { - - // changes password from to - - if (request_receive_mode()) { +// changes password from to +static int write_password(uint32_t password, uint32_t new_password) { + 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 +1116,169 @@ 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; } +// write operation process for EM4x50 tag, +// single word is written to given address, verified by selective read operation +// wrong password -> return with PM3_EFAILED void em4x50_write(em4x50_data_t *etd) { + int status = PM3_EFAILED; + uint32_t words[EM4X50_NO_WORDS] = {0x0}; - // write operation process for EM4x50 tag, - // single word is written to given address, verified by selective read operation - - 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}; - - 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()) { - - // if password is given login - if (etd->pwd_given) - blogin &= login(etd->password); - - // call a selective read - addresses[2] = addresses[3] = etd->address; - if (selective_read(addresses)) { - - // 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); - - bsuccess = true; - for (int i = 0; i < 4; i++) - bsuccess &= (word[i] == etd->word[i]) ? true : false; - - } - } - } - } - - status = (bsuccess << 1) + blogin; - lf_finalize(); - reply_ng(CMD_LF_EM4X50_WRITE, status, (uint8_t *)tag.sectors, 238); -} - -void em4x50_write_password(em4x50_data_t *etd) { - - // simple change of password - - 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)) { - - int res = write_password(etd->password, etd->new_password); - if (res == PM3_ETEAROFF) { + // write word to given address + status = write(etd->word, etd->addresses); + if (status == PM3_ETEAROFF) { lf_finalize(); return; } - bsuccess = (res == PM3_SUCCESS); - } - } - lf_finalize(); - reply_ng(CMD_LF_EM4X50_WRITE_PASSWORD, bsuccess, 0, 0); -} + if (status == PM3_SUCCESS) { -void em4x50_wipe(em4x50_data_t *etd) { + // to verify result reset EM4x50 + status = reset(); + if (status == PM3_SUCCESS) { - // set all data of EM4x50 tag to 0x0 including password + // if password is given renew login after reset + if (etd->pwd_given) + status = login(etd->password1); + + if (status == PM3_SUCCESS) { - bool bsuccess = false; - uint8_t zero[4] = {0, 0, 0, 0}; - uint8_t addresses[4] = {0, 0, EM4X50_NO_WORDS - 3, 1}; + // call a selective read + status = selective_read(etd->addresses, words); + if (status == PM3_SUCCESS) { - init_tag(); - em4x50_setup_read(); - - // set gHigh and gLow - if (get_signalproperties() && find_em4x50_tag()) { - - // login first - if (login(etd->password)) { - - // 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); - - // to verify result reset EM4x50 - if (reset()) { - - // 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)) { - - // 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; - - } - - if (bsuccess) { - - // so far everything is fine - // last task: reset password - if (login(etd->password)) - bsuccess = write_password(etd->password, zero); - - // verify by login with new password - if (bsuccess) - bsuccess = login(zero); + // compare result with given word + if (words[etd->addresses & 0xFF] != reflect32(etd->word)) + status = PM3_EFAILED; + } + } } } } } lf_finalize(); - reply_ng(CMD_LF_EM4X50_WIPE, bsuccess, (uint8_t *)tag.sectors, 238); + reply_ng(CMD_LF_EM4X50_WRITE, status, (uint8_t *)words, 136); +} + +// simple change of password +void em4x50_writepwd(em4x50_data_t *etd) { + int status = PM3_EFAILED; + + em4x50_setup_read(); + + if (get_signalproperties() && find_em4x50_tag()) { + + // login and change password + if (login(etd->password1) == PM3_SUCCESS) { + + status = write_password(etd->password1, etd->password2); + if (status == PM3_ETEAROFF) { + lf_finalize(); + return; + } + } + } + + lf_finalize(); + reply_ng(CMD_LF_EM4X50_WRITEPWD, status, NULL, 0); +} + +// simulate uploaded data in emulator memory +// (currently simulation allows only a one-way communication) +void em4x50_sim(uint8_t *filename) { + int status = PM3_SUCCESS; + uint8_t *em4x50_mem = BigBuf_get_EM_addr(); + uint32_t words[EM4X50_NO_WORDS] = {0x0}; + +#ifdef WITH_FLASH + + if (strlen((char *)filename) != 0) { + + BigBuf_free(); + + int changed = rdv40_spiffs_lazy_mount(); + uint32_t size = size_in_spiffs((char *)filename); + em4x50_mem = BigBuf_malloc(size); + + rdv40_spiffs_read_as_filetype((char *)filename, em4x50_mem, size, RDV40_SPIFFS_SAFETY_SAFE); + + if (changed) + rdv40_spiffs_lazy_unmount(); + } + +#endif + + 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]) { + + // 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(); + + // iceman, will need a usb cmd check to break as well + while (BUTTON_PRESS() == false) { + + WDT_HIT(); + em4x50_sim_send_listen_window(); + for (int i = fwr; i <= lwr; i++) { + + em4x50_sim_send_listen_window(); + + 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_SIM, status, NULL, 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/em4x70.c b/armsrc/em4x70.c new file mode 100644 index 000000000..1feb448c7 --- /dev/null +++ b/armsrc/em4x70.c @@ -0,0 +1,563 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins based on em4x50 +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Low frequency EM4170 commands +//----------------------------------------------------------------------------- + +#include "fpgaloader.h" +#include "ticks.h" +#include "dbprint.h" +#include "lfadc.h" +#include "commonutil.h" +#include "em4x70.h" +#include "appmain.h" // tear + +static em4x70_tag_t tag = { 0 }; + +// EM4170 requires a parity bit on commands, other variants do not. +static bool command_parity = true; + +#define EM4X70_T_TAG_QUARTER_PERIOD 8 +#define EM4X70_T_TAG_HALF_PERIOD 16 +#define EM4X70_T_TAG_THREE_QUARTER_PERIOD 24 +#define EM4X70_T_TAG_FULL_PERIOD 32 +#define EM4X70_T_TAG_TWA 128 // Write Access Time +#define EM4X70_T_TAG_DIV 224 // Divergency Time +#define EM4X70_T_TAG_AUTH 4224 // Authentication Time +#define EM4X70_T_TAG_WEE 3072 // EEPROM write Time +#define EM4X70_T_TAG_TWALB 128 // Write Access Time of Lock Bits + +#define EM4X70_T_WAITING_FOR_SNGLLIW 160 // Unsure + +#define TICKS_PER_FC 12 // 1 fc = 8us, 1.5us per tick = 12 ticks +#define EM4X70_MIN_AMPLITUDE 10 // Minimum difference between a high and low signal + +#define EM4X70_TAG_TOLERANCE 10 +#define EM4X70_TAG_WORD 48 + + +/** + * These IDs are from the EM4170 datasheet + * Some versions of the chip require a fourth + * (even) parity bit, others do not + */ +#define EM4X70_COMMAND_ID 0x01 +#define EM4X70_COMMAND_UM1 0x02 +#define EM4X70_COMMAND_AUTH 0x03 +#define EM4X70_COMMAND_PIN 0x04 +#define EM4X70_COMMAND_WRITE 0x05 +#define EM4X70_COMMAND_UM2 0x07 + +static uint8_t gHigh = 0; +static uint8_t gLow = 0; + +#define IS_HIGH(sample) (sample>gLow ? true : false) +#define IS_LOW(sample) (sample timeout_ticks) + + +static uint8_t bits2byte(uint8_t *bits, int length); +static void bits2bytes(uint8_t *bits, int length, uint8_t *out); +static int em4x70_receive(uint8_t *bits); +static bool find_listen_window(bool command); + +static void init_tag(void) { + memset(tag.data, 0x00, sizeof(tag.data)/sizeof(tag.data[0])); +} + +static void EM4170_setup_read(void) { + + FpgaDownloadAndGo(FPGA_BITSTREAM_LF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC | FPGA_LF_ADC_READER_FIELD); + + // 50ms for the resonant antenna to settle. + SpinDelay(50); + + // Now set up the SSC to get the ADC samples that are now streaming at us. + FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER); + + FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); + + // Connect the A/D to the peak-detected low-frequency path. + SetAdcMuxFor(GPIO_MUXSEL_LOPKD); + + // Steal this pin from the SSP (SPI communication channel with fpga) and + // use it to control the modulation + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + + // Disable modulation at default, which means enable the field + LOW(GPIO_SSC_DOUT); + + // Start the timer + StartTicks(); + + // Watchdog hit + WDT_HIT(); +} + +static bool get_signalproperties(void) { + + // calculate signal properties (mean amplitudes) from measured data: + // 32 amplitudes (maximum values) -> mean amplitude value -> gHigh -> gLow + bool signal_found = false; + int no_periods = 32, pct = 50, noise = 140; // pct originally 75, found 50 was working better for me + uint8_t sample_ref = 127; + uint8_t sample_max_mean = 0; + uint8_t sample_max[no_periods]; + uint32_t sample_max_sum = 0; + + memset(sample_max, 0x00, sizeof(sample_max)); + + // wait until signal/noise > 1 (max. 32 periods) + for (int i = 0; i < TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD * no_periods; i++) { + + // about 2 samples per bit period + WaitTicks(TICKS_PER_FC * EM4X70_T_TAG_HALF_PERIOD); + + if (AT91C_BASE_SSC->SSC_RHR > noise) { + signal_found = true; + break; + } + + } + + if (signal_found == false) + return false; + + // calculate mean maximum value of 32 periods, each period has a length of + // 3 single "full periods" to eliminate the influence of a listen window + for (int i = 0; i < no_periods; i++) { + + uint32_t start_ticks = GetTicks(); + //AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (GetTicks() - start_ticks < TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD) { + + volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + + if (sample > sample_max[i]) + sample_max[i] = sample; + + } + + sample_max_sum += sample_max[i]; + } + + sample_max_mean = sample_max_sum / no_periods; + + // set global envelope variables + gHigh = sample_ref + pct * (sample_max_mean - sample_ref) / 100; + gLow = sample_ref - pct * (sample_max_mean - sample_ref) / 100; + + // Basic sanity check + if(gHigh - gLow < EM4X70_MIN_AMPLITUDE) { + return false; + } + + Dbprintf("%s: gHigh %d gLow: %d", __func__, gHigh, gLow); + return true; +} + +/** + * get_pulse_length + * + * Times falling edge pulses + */ +static uint32_t get_pulse_length(void) { + + uint8_t sample; + uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + uint32_t start_ticks = GetTicks(); + timeout = start_ticks + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + timeout = (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD) + GetTicks(); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + return GetTicks() - start_ticks; +} + +/** + * get_pulse_invert_length + * + * Times rising edge pules + * TODO: convert to single function with get_pulse_length() + */ +static uint32_t get_pulse_invert_length(void) { + + uint8_t sample; + uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + uint32_t start_ticks = GetTicks(); + timeout = start_ticks + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + do { + sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; + }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + + if (IS_TIMEOUT(timeout)) + return 0; + + return GetTicks() - start_ticks; + +} + +static bool check_pulse_length(uint32_t pl, int length, int margin) { + // check if pulse length corresponds to given length + //Dbprintf("%s: pulse length %d vs %d", __func__, pl, length * TICKS_PER_FC); + return ((pl >= TICKS_PER_FC * (length - margin)) & (pl <= TICKS_PER_FC * (length + margin))); +} + +static void em4x70_send_bit(int bit) { + + // send single bit according to EM4170 application note and datasheet + + uint32_t start_ticks = GetTicks(); + + if (bit == 0) { + + // disable modulation (drop the field) for 4 cycles of carrier + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * 4); + + // enable modulation (activates the field) for remaining first + // half of bit period + HIGH(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_HALF_PERIOD); + + // disable modulation for second half of bit period + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD); + + } else { + + // bit = "1" means disable modulation for full bit period + LOW(GPIO_SSC_DOUT); + while (GetTicks() - start_ticks <= TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD); + } + +} + + +/** + * em4x70_send_command + */ +static void em4170_send_command(uint8_t command) { + int parity = 0; + int msb_bit = 0; + + // Non automotive EM4x70 based tags are 3 bits + 1 parity. + // So drop the MSB and send a parity bit instead after the command + if(command_parity) + msb_bit = 1; + + for (int i = msb_bit; i < 4; i++) { + int bit = (command >> (3 - i)) & 1; + em4x70_send_bit(bit); + parity ^= bit; + } + + if(command_parity) + em4x70_send_bit(parity); + +} + +static bool find_listen_window(bool command) { + + int cnt = 0; + while(cnt < EM4X70_T_WAITING_FOR_SNGLLIW) { + /* + 80 ( 64 + 16 ) + 80 ( 64 + 16 ) + Flip Polarity + 96 ( 64 + 32 ) + 64 ( 32 + 16 +16 )*/ + + if (check_pulse_length(get_pulse_invert_length(), 80, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_invert_length(), 80, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_length(), 96, EM4X70_TAG_TOLERANCE)) { + if (check_pulse_length(get_pulse_length(), 64, EM4X70_TAG_TOLERANCE)) { + if(command) { + /* Here we are after the 64 duration edge. + * em4170 says we need to wait about 48 RF clock cycles. + * depends on the delay between tag and us + * + * I've found between 4-5 quarter periods (32-40) works best + */ + WaitTicks(TICKS_PER_FC * 5 * EM4X70_T_TAG_QUARTER_PERIOD); + // Send RM Command + em4x70_send_bit(0); + em4x70_send_bit(0); + } + return true; + } + } + } + } + cnt++; + } + + return false; +} + +static void bits2bytes(uint8_t *bits, int length, uint8_t *out) { + + if(length%8 != 0) { + Dbprintf("Should have a multiple of 8 bits, was sent %d", length); + } + + int num_bytes = length / 8; // We should have a multiple of 8 here + + for(int i=1; i <= num_bytes; i++) { + out[num_bytes-i] = bits2byte(bits, 8); + bits+=8; + //Dbprintf("Read: %02X", out[num_bytes-i]); + } +} + +static uint8_t bits2byte(uint8_t *bits, int length) { + + // converts separate bits into a single "byte" + uint8_t byte = 0; + for (int i = 0; i < length; i++) { + + byte |= bits[i]; + + if (i != length - 1) + byte <<= 1; + } + + return byte; +} + +/*static void print_array(uint8_t *bits, int len) { + + if(len%8 != 0) { + Dbprintf("Should have a multiple of 8 bits, was sent %d", len); + } + + int num_bytes = len / 8; // We should have a multiple of 8 here + + uint8_t bytes[8]; + + for(int i=0;i speed up "lf search" process + return find_listen_window(false); +} + +static int em4x70_receive(uint8_t *bits) { + + uint32_t pl; + int bit_pos = 0; + uint8_t edge = 0; + + + bool foundheader = false; + + // Read out the header + // 12 Manchester 1's (may miss some during settle period) + // 4 Manchester 0's + + // Skip a few leading 1's as it could be noisy + WaitTicks(TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + + // wait until we get the transition from 1's to 0's which is 1.5 full windows + int pulse_count = 0; + while(pulse_count < 12){ + pl = get_pulse_invert_length(); + pulse_count++; + if(check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD, EM4X70_TAG_TOLERANCE)) { + foundheader = true; + break; + } + } + + if(!foundheader) { + Dbprintf("Failed to find read header"); + return 0; + } + + // Skip next 3 0's, header check consumes the first 0 + for(int i = 0; i < 3; i++) { + get_pulse_invert_length(); + } + + // identify remaining bits based on pulse lengths + // between two listen windows only pulse lengths of 1, 1.5 and 2 are possible + while (true) { + + if(edge) + pl = get_pulse_length(); + else + pl = get_pulse_invert_length(); + + if (check_pulse_length(pl, EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length = 1 + bits[bit_pos++] = edge; + + } else if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length = 1.5 -> flip edge detection + if(edge) { + bits[bit_pos++] = 0; + bits[bit_pos++] = 0; + edge = 0; + } else { + bits[bit_pos++] = 1; + bits[bit_pos++] = 1; + edge = 1; + } + + } else if (check_pulse_length(pl, 2 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + + // pulse length of 2 + if(edge) { + bits[bit_pos++] = 0; + bits[bit_pos++] = 1; + } else { + bits[bit_pos++] = 1; + bits[bit_pos++] = 0; + } + + } else if ( (edge && check_pulse_length(pl, 3 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) || + (!edge && check_pulse_length(pl, 80, EM4X70_T_TAG_QUARTER_PERIOD))) { + + // LIW detected (either invert or normal) + return --bit_pos; + } + } + return bit_pos; + +} + +void em4x70_info(em4x70_data_t *etd) { + + uint8_t status = 0; + + // Support tags with and without command parity bits + command_parity = etd->parity; + + init_tag(); + EM4170_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_EM4X70_Tag()) { + // Read ID, UM1 and UM2 + status = em4x70_read_id() && em4x70_read_um1() && em4x70_read_um2(); + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, sizeof(tag.data)); +} diff --git a/armsrc/em4x70.h b/armsrc/em4x70.h new file mode 100644 index 000000000..80fd977a9 --- /dev/null +++ b/armsrc/em4x70.h @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#ifndef EM4x70_H +#define EM4x70_H + +#include "../include/em4x70.h" + +typedef struct { + uint8_t data[32]; +} em4x70_tag_t; + +void em4x70_info(em4x70_data_t *etd); + +#endif /* EM4x70_H */ diff --git a/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/CMakeLists.txt b/client/CMakeLists.txt index b630bf5de..4ea7acf36 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -252,6 +252,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdlfem410x.c ${PM3_ROOT}/client/src/cmdlfem4x05.c ${PM3_ROOT}/client/src/cmdlfem4x50.c + ${PM3_ROOT}/client/src/cmdlfem4x70.c ${PM3_ROOT}/client/src/cmdlffdxb.c ${PM3_ROOT}/client/src/cmdlfgallagher.c ${PM3_ROOT}/client/src/cmdlfguard.c diff --git a/client/Makefile b/client/Makefile index d577b0d25..9c624722c 100644 --- a/client/Makefile +++ b/client/Makefile @@ -493,6 +493,7 @@ SRCS = aiddesfire.c \ cmdlfem410x.c \ cmdlfem4x05.c \ cmdlfem4x50.c \ + cmdlfem4x70.c \ cmdlffdxb.c \ cmdlfguard.c \ cmdlfgallagher.c \ diff --git a/client/resources/mad.json b/client/resources/mad.json index 535407ab4..d5c413d73 100644 --- a/client/resources/mad.json +++ b/client/resources/mad.json @@ -4066,6 +4066,13 @@ "service_provider": "PAYCULT", "system_integrator": "PAYCULT" }, + { + "application": "Access Control (SIO)", + "company": "HID Global", + "mad": "0x3D01", + "service_provider": "HID Corporation", + "system_integrator": "HID Corporation" + }, { "application": "City transport bus, ferry, administration", "company": "VFJ Technology Pty Ltd", diff --git a/client/src/cmddata.c b/client/src/cmddata.c index d1855aaff..daff73722 100644 --- a/client/src/cmddata.c +++ b/client/src/cmddata.c @@ -709,7 +709,7 @@ static int Cmdmandecoderaw(const char *Cmd) { if (Em410xDecode(bits, &size, &idx, &hi, &id) == 1) { //need to adjust to set bitstream back to manchester encoded data //setDemodBuff(bits, size, idx); - printEM410x(hi, id); + printEM410x(hi, id, false); } } return PM3_SUCCESS; diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index e5d5042f8..576979aad 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -11,12 +11,10 @@ // High frequency ISO14443A commands //----------------------------------------------------------------------------- #include "cmdhf14a.h" - #include #include - #include "cmdparser.h" // command_t -#include "commonutil.h" // ARRAYLEN +#include "commonutil.h" // ARRAYLEN #include "comms.h" // clearCommandBuffer #include "cmdtrace.h" #include "cliparser.h" @@ -29,6 +27,9 @@ #include "aidsearch.h" #include "cmdhf.h" // handle HF plot #include "protocols.h" // MAGIC_GEN_1A +#include "cliparser.h" +#include "protocols.h" // definitions of ISO14A/7816 protocol +#include "emv/apduinfo.h" // GetAPDUCodeDescription bool APDUInFramingEnable = true; @@ -2119,6 +2120,139 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { return select_status; } +static uint16_t get_sw(uint8_t *d, uint8_t n) { + if (n < 2) + return 0; + + n -= 2; + return d[n] * 0x0100 + d[n + 1]; +} +static int CmdHf14AFuzzapdu(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf 14a apdufuzz", + "Fuzz APDU's of ISO7816 protocol to find valid CLS/INS/P1P2/LE commands.\n" + "It loops all 256 possible values for each byte.\n" + "Tag must be on antenna before running.", + "hf 14a apdufuzz\n" + "hf 14a apdufuzz --cla 80\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "cla", "", "start CLASS value (1 hex byte)"), + arg_str0(NULL, "ins", "", "start INSTRUCTION value (1 hex byte)"), + arg_str0(NULL, "p1", "", "start P1 value (1 hex byte)"), + arg_str0(NULL, "p2", "", "start P2 value (1 hex byte)"), + arg_str0(NULL, "le", "", "start LENGTH value (1 hex byte)"), + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int cla_len = 0; + uint8_t cla[1] = {0}; + CLIGetHexWithReturn(ctx, 1, cla, &cla_len); + + int ins_len = 0; + uint8_t ins[1] = {0}; + CLIGetHexWithReturn(ctx, 2, ins, &ins_len); + + int p1_len = 0; + uint8_t p1[1] = {0}; + CLIGetHexWithReturn(ctx, 3, p1, &p1_len); + + int p2_len = 0; + uint8_t p2[1] = {0}; + CLIGetHexWithReturn(ctx, 4, p2, &p2_len); + + int le_len = 0; + uint8_t le[1] = {0}; + CLIGetHexWithReturn(ctx, 5, le, &le_len); + + bool verbose = arg_get_lit(ctx, 6); + CLIParserFree(ctx); + + bool activate_field = true; + bool keep_field_on = true; + + uint8_t a = cla[0]; + uint8_t b = ins[0]; + uint8_t c = p1[0]; + uint8_t d = p2[0]; + uint8_t e = le[0]; + + PrintAndLogEx(SUCCESS, "Starting the apdu fuzzer [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " LE " _GREEN_("%02x")" ]", a,b,c,d,e); + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + + uint8_t response[PM3_CMD_DATA_SIZE]; + int resplen = 0; + + uint8_t aSELECT_AID[80]; + int aSELECT_AID_n = 0; + param_gethex_to_eol("00a404000aa000000440000101000100", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); + int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen); + if (res) { + DropField(); + return res; + } + + if (activate_field) + activate_field = false; + + uint64_t t1 = msclock(); + do { + do { + do { + do { + do { + if (kbd_enter_pressed()) { + goto out; + } + + uint8_t foo[5] = {a, b, c, d, e}; + int foo_n = sizeof(foo); + + if (verbose) { + PrintAndLogEx(INFO, "%s", sprint_hex(foo, sizeof(foo))); + } + res = ExchangeAPDU14a(foo, foo_n, activate_field, keep_field_on, response, sizeof(response), &resplen); + if (res) { + e++; + continue; + } + + uint16_t sw = get_sw(response, resplen); + if (sw != 0x6a86 && + sw != 0x6986 && + sw != 0x6d00 + ) { + PrintAndLogEx(INFO, "%02X %02X %02X %02X %02X (%04x - %s)", a,b,c,d,e, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } + e++; + if (verbose) { + PrintAndLogEx(INFO, "Status: %02X %02X %02X %02X %02X", a,b,c,d,e); + } + + } while (e); + d++; + PrintAndLogEx(INFO, "Status: %02X %02X %02X %02X %02X", a,b,c,d,e); + } while (d); + c++; + PrintAndLogEx(INFO, "Status: %02X %02X %02X %02X %02X", a,b,c,d,e); + } while (c); + b++; + PrintAndLogEx(INFO, "Status: %02X %02X %02X %02X %02X", a,b,c,d,e); + } while (b); + a++; + PrintAndLogEx(INFO, "Status: %02X %02X %02X %02X %02X", a,b,c,d,e); + } while(a); + +out: + PrintAndLogEx(SUCCESS, "time: %" PRIu64 " seconds\n", (msclock() - t1) / 1000); + DropField(); + return PM3_SUCCESS; +} + static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdHF14AList, AlwaysAvailable, "List ISO 14443-a history"}, @@ -2132,6 +2266,7 @@ static command_t CommandTable[] = { {"raw", CmdHF14ACmdRaw, IfPm3Iso14443a, "Send raw hex data to tag"}, {"antifuzz", CmdHF14AAntiFuzz, IfPm3Iso14443a, "Fuzzing the anticollision phase. Warning! Readers may react strange"}, {"config", CmdHf14AConfig, IfPm3Iso14443a, "Configure 14a settings (use with caution)"}, + {"apdufuzz", CmdHf14AFuzzapdu, IfPm3Iso14443a, "Fuzz APDU - CLA/INS/P1P2"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index cd6017c8a..d884e7184 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1696,7 +1696,7 @@ static int CmdHFiClassRestore(const char *Cmd) { CLIParserInit(&ctx, "hf iclass restore", "Restore data from dumpfile onto a iCLASS tag", "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0\n" - "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0 --elite" + "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0 --elite\n" "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 -k 1122334455667788 --elite\n" ); diff --git a/client/src/cmdhfthinfilm.c b/client/src/cmdhfthinfilm.c index 34001be44..48844b759 100644 --- a/client/src/cmdhfthinfilm.c +++ b/client/src/cmdhfthinfilm.c @@ -12,7 +12,7 @@ #include #include #include - +#include "cliparser.h" #include "cmdparser.h" // command_t #include "comms.h" #include "cmdtrace.h" @@ -22,28 +22,6 @@ static int CmdHelp(const char *Cmd); -static int usage_thinfilm_info(void) { - PrintAndLogEx(NORMAL, "Usage: hf thinfilm info [h]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h this help"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, " hf thinfilm info"); - return PM3_SUCCESS; -} - -static int usage_thinfilm_sim(void) { - PrintAndLogEx(NORMAL, "Usage: hf thinfilm sim [h] [d ]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h this help"); - PrintAndLogEx(NORMAL, " d bytes to send, in hex"); - PrintAndLogEx(NORMAL, " r raw, provided bytes should include CRC"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, " hf thinfilm sim d B70470726f786d61726b2e636f6d"); - return PM3_SUCCESS; -} - // Printing function based upon the code in libnfc // ref // https://github.com/nfc-tools/libnfc/blob/master/utils/nfc-barcode.c @@ -119,26 +97,17 @@ static int print_barcode(uint8_t *barcode, const size_t barcode_len, bool verbos } static int CmdHfThinFilmInfo(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf thinfilm info", + "Get info from Thinfilm tags", + "hf thinfilm info"); - uint8_t cmdp = 0; - bool errors = false; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_thinfilm_info(); - default: - PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } - - //Validations - if (errors) { - usage_thinfilm_info(); - return PM3_EINVARG; - } - + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); return infoThinFilm(true); } @@ -168,45 +137,40 @@ int infoThinFilm(bool verbose) { } static int CmdHfThinFilmSim(const char *Cmd) { - uint8_t cmdp = 0; - uint8_t data[512]; - int datalen = 0; + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf thinfilm sim", + "Simulate Thinfilm tag", + "hf thinfilm sim -d B70470726f786d61726b2e636f6d"); + + void *argtable[] = { + arg_param_begin, + arg_str1("d", "data", "", "bytes to send"), + arg_lit0(NULL, "raw", "raw, provided bytes should include CRC"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int data_len = 0; + uint8_t data[512] = {0}; + CLIGetHexWithReturn(ctx, 1, data, &data_len); bool addcrc = true; - bool errors = false; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_thinfilm_sim(); - case 'd': - // Retrieve the data - param_gethex_ex(Cmd, cmdp + 1, data, &datalen); - datalen >>= 1; - cmdp += 2; - break; - case 'r': - addcrc = false; - cmdp++; - break; - default: - PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - } + if (arg_get_lit(ctx, 2)) { + addcrc = false; } - //Validations - if (errors || cmdp == 0 || datalen == 0 || datalen > 512) return usage_thinfilm_sim(); - if (addcrc && datalen <= 510) { + CLIParserFree(ctx); + + if (addcrc && data_len <= 510) { uint8_t b1, b2; - compute_crc(CRC_14443_A, data, datalen, &b1, &b2); - data[datalen++] = b2; - data[datalen++] = b1; + compute_crc(CRC_14443_A, data, data_len, &b1, &b2); + data[data_len++] = b2; + data[data_len++] = b1; } clearCommandBuffer(); - SendCommandNG(CMD_HF_THINFILM_SIMULATE, (uint8_t *)&data, datalen); + SendCommandNG(CMD_HF_THINFILM_SIMULATE, (uint8_t *)&data, data_len); PacketResponseNG resp; PrintAndLogEx(SUCCESS, "press pm3-button to abort simulation"); diff --git a/client/src/cmdhftopaz.c b/client/src/cmdhftopaz.c index a64071c97..ba150cb71 100644 --- a/client/src/cmdhftopaz.c +++ b/client/src/cmdhftopaz.c @@ -14,6 +14,7 @@ #include #include #include +#include "cliparser.h" #include "cmdparser.h" // command_t #include "comms.h" #include "cmdtrace.h" @@ -393,21 +394,42 @@ static int topaz_print_NDEF(uint8_t *data, size_t maxsize) { } static int CmdHFTopazReader(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz reader", + "Read UID from Topaz tags", + "hf topaz reader"); - bool verbose = true; - char ctmp = tolower(param_getchar(Cmd, 0)); - if (ctmp == 's') verbose = false; + void *argtable[] = { + arg_param_begin, + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool verbose = arg_get_lit(ctx, 1); + + CLIParserFree(ctx); return readTopazUid(verbose); } // read a Topaz tag and print some useful information static int CmdHFTopazInfo(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz info", + "Get info from Topaz tags", + "hf topaz info"); - bool verbose = true; - char ctmp = tolower(param_getchar(Cmd, 0)); - if (ctmp == 's') verbose = false; + void *argtable[] = { + arg_param_begin, + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool verbose = arg_get_lit(ctx, 1); + + CLIParserFree(ctx); int status = readTopazUid(verbose); if (status != PM3_SUCCESS) @@ -469,13 +491,34 @@ static int CmdHFTopazInfo(const char *Cmd) { } static int CmdHFTopazSim(const char *Cmd) { - (void)Cmd; // Cmd is not used so far + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz sim", + "Simulate a Topaz tag", + "hf topaz sim <- Not yet implemented"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); PrintAndLogEx(INFO, "not yet implemented"); return PM3_SUCCESS; } static int CmdHFTopazCmdRaw(const char *Cmd) { - (void)Cmd; // Cmd is not used so far + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz raw", + "Send raw hex data to Topaz tags", + "hf topaz raw <- Not yet implemented"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + PrintAndLogEx(INFO, "not yet implemented. Use hf 14 raw with option -T."); return PM3_SUCCESS; } @@ -490,6 +533,25 @@ static int CmdHFTopazList(const char *Cmd) { return CmdTraceList(args); } +static int CmdHFTopazSniff(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf topaz sniff", + "Sniff Topaz reader-tag communication", + "hf topaz sniff"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + + uint8_t param = 0; + SendCommandNG(CMD_HF_ISO14443A_SNIFF, (uint8_t *)¶m, sizeof(uint8_t)); + + return PM3_SUCCESS; +} + static int CmdHelp(const char *Cmd); static command_t CommandTable[] = { @@ -498,7 +560,7 @@ static command_t CommandTable[] = { {"info", CmdHFTopazInfo, IfPm3Iso14443a, "Tag information"}, {"reader", CmdHFTopazReader, IfPm3Iso14443a, "Act like a Topaz reader"}, {"sim", CmdHFTopazSim, IfPm3Iso14443a, " -- Simulate Topaz tag"}, - {"sniff", CmdHF14ASniff, IfPm3Iso14443a, "Sniff Topaz reader-tag communication"}, + {"sniff", CmdHFTopazSniff, IfPm3Iso14443a, "Sniff Topaz reader-tag communication"}, {"raw", CmdHFTopazCmdRaw, IfPm3Iso14443a, "Send raw hex data to tag"}, {NULL, NULL, 0, NULL} }; diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index cbecb8f87..89483bf00 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -31,6 +31,7 @@ #include "cmdlfem410x.h" // for em4x menu #include "cmdlfem4x05.h" // for em4x05 / 4x69 #include "cmdlfem4x50.h" // for em4x50 +#include "cmdlfem4x70.h" // for em4x70 #include "cmdlfhid.h" // for hid menu #include "cmdlfhitag.h" // for hitag menu #include "cmdlfidteck.h" // for idteck menu @@ -1370,6 +1371,14 @@ static bool CheckChipType(bool getDeviceData) { goto out; } + // check for em4x70 chips + if (detect_4x70_block()) { + PrintAndLogEx(SUCCESS, "Chipset detection: " _GREEN_("EM4x70")); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf em 4x70`") " commands"); + retval = true; + goto out; + } + PrintAndLogEx(NORMAL, "Couldn't identify a chipset"); out: save_restoreGB(GRAPH_RESTORE); diff --git a/client/src/cmdlfem.c b/client/src/cmdlfem.c index a89a4e2be..7ad0da3dc 100644 --- a/client/src/cmdlfem.c +++ b/client/src/cmdlfem.c @@ -12,6 +12,8 @@ #include "cmdlfem410x.h" #include "cmdlfem4x05.h" #include "cmdlfem4x50.h" +#include "cmdlfem4x70.h" + #include #include #include "cmdparser.h" // command_t @@ -25,6 +27,7 @@ static command_t CommandTable[] = { {"410x", CmdLFEM410X, AlwaysAvailable, "EM 4102 commands..."}, {"4x05", CmdLFEM4X05, AlwaysAvailable, "EM 4205 / 4305 / 4369 / 4469 commands..."}, {"4x50", CmdLFEM4X50, AlwaysAvailable, "EM 4350 / 4450 commands..."}, + {"4x70", CmdLFEM4X70, AlwaysAvailable, "EM 4070 / 4170 commands..."}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index ef35e95a0..790e3e070 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -1,5 +1,8 @@ //----------------------------------------------------------------------------- // Copyright (C) 2010 iZsh + +// modified marshmellow +// modified Iceman, 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 @@ -10,13 +13,11 @@ #include "cmdlfem410x.h" #include "cmdlfem4x50.h" - #include #include #include #include #include - #include "fileutils.h" #include "cmdparser.h" // command_t #include "comms.h" @@ -37,88 +38,6 @@ static uint64_t g_em410xid = 0; static int CmdHelp(const char *Cmd); - -//////////////// 410x commands -static int usage_lf_em410x_demod(void) { - PrintAndLogEx(NORMAL, "Usage: lf em 410x_demod [h] [clock] <0|1> [maxError]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " clock - set clock as integer, optional, if not set, autodetect."); - PrintAndLogEx(NORMAL, " <0|1> - 0 normal output, 1 for invert output"); - PrintAndLogEx(NORMAL, " maxerror - set maximum allowed errors, default = 100."); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_demod") " = demod an EM410x Tag ID from GraphBuffer"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_demod 32") " = demod an EM410x Tag ID from GraphBuffer using a clock of RF/32"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_demod 32 1") " = demod an EM410x Tag ID from GraphBuffer using a clock of RF/32 and inverting data"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_demod 1") " = demod an EM410x Tag ID from GraphBuffer while inverting data"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_demod 64 1 0") " = demod an EM410x Tag ID from GraphBuffer using a clock of RF/64 and inverting data and allowing 0 demod errors"); - return PM3_SUCCESS; -} -static int usage_lf_em410x_watch(void) { - PrintAndLogEx(NORMAL, "Enables IOProx compatible reader mode printing details of scanned tags."); - PrintAndLogEx(NORMAL, "By default, values are printed and logged until the button is pressed or another USB command is issued."); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 410x_watch"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_watch")); - return PM3_SUCCESS; -} - -static int usage_lf_em410x_clone(void) { - PrintAndLogEx(NORMAL, "Writes EM410x ID to a T55x7 or Q5/T5555 tag"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 410x_clone [h] [clock]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " - ID number"); - PrintAndLogEx(NORMAL, " - 0|1 0 = Q5/T5555, 1 = T55x7"); - PrintAndLogEx(NORMAL, " - 16|32|40|64, optional, set R/F clock rate, defaults to 64"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_clone 0F0368568B 1") " = write ID to t55x7 card"); - return PM3_SUCCESS; -} -static int usage_lf_em410x_ws(void) { - PrintAndLogEx(NORMAL, "Watch 'nd Spoof, activates reader, waits until a EM410x tag gets presented then it starts simulating the found UID"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 410x_spoof [h]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_spoof")); - return PM3_SUCCESS; -} -static int usage_lf_em410x_sim(void) { - PrintAndLogEx(NORMAL, "Simulating EM410x tag"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 410x_sim [h] "); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " uid - uid (10 HEX symbols)"); - PrintAndLogEx(NORMAL, " clock - clock (32|64) (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_sim 0F0368568B")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_sim 0F0368568B 32")); - return PM3_SUCCESS; -} -static int usage_lf_em410x_brute(void) { - PrintAndLogEx(NORMAL, "Bruteforcing by emulating EM410x tag"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, "Usage: lf em 410x_brute [h] ids.txt [d 2000] [c clock]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h - this help"); - PrintAndLogEx(NORMAL, " ids.txt - file with UIDs in HEX format, one per line"); - PrintAndLogEx(NORMAL, " d (2000) - pause delay in milliseconds between UIDs simulation, default 1000 ms (optional)"); - PrintAndLogEx(NORMAL, " c (32) - clock (32|64), default 64 (optional)"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_brute ids.txt")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_brute ids.txt c 32")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_brute ids.txt d 3000")); - PrintAndLogEx(NORMAL, _YELLOW_(" lf em 410x_brute ids.txt d 3000 c 32")); - return PM3_SUCCESS; -} - /* Read the ID of an EM410x tag. * Format: * 1111 1111 1 <-- standard non-repeatable header @@ -129,63 +48,62 @@ static int usage_lf_em410x_brute(void) { */ // Construct the graph for emulating an EM410X tag -static void ConstructEM410xEmulGraph(const char *uid, const uint8_t clock) { +static void em410x_construct_emul_graph(uint8_t *uid, uint8_t clock) { - int i, j, binary[4], parity[4]; - uint32_t n; - /* clear our graph */ + // clear our graph ClearGraph(true); - /* write 16 zero bit sledge */ - for (i = 0; i < 20; i++) + // write 16 zero bit sledge + for (uint8_t i = 0; i < 20; i++) AppendGraph(false, clock, 0); - /* write 9 start bits */ - for (i = 0; i < 9; i++) + // write 9 start bits + for (uint8_t i = 0; i < 9; i++) AppendGraph(false, clock, 1); - /* for each hex char */ - parity[0] = parity[1] = parity[2] = parity[3] = 0; - for (i = 0; i < 10; i++) { - /* read each hex char */ - sscanf(&uid[i], "%1x", &n); - for (j = 3; j >= 0; j--, n /= 2) - binary[j] = n % 2; + uint8_t bs[8], parity[8]; + memset(parity, 0, sizeof(parity)); - /* append each bit */ - AppendGraph(false, clock, binary[0]); - AppendGraph(false, clock, binary[1]); - AppendGraph(false, clock, binary[2]); - AppendGraph(false, clock, binary[3]); + for (uint8_t i = 0; i < 5; i++) { - /* append parity bit */ - AppendGraph(false, clock, binary[0] ^ binary[1] ^ binary[2] ^ binary[3]); + for (uint8_t j = 0; j < 8; j++) { + bs[j] = (uid[i] >> (7 - j) & 1); + } + PrintAndLogEx(DEBUG, "uid[%d] 0x%02x (%s)", i, uid[i], sprint_bin(bs, 4)); - /* keep track of column parity */ - parity[0] ^= binary[0]; - parity[1] ^= binary[1]; - parity[2] ^= binary[2]; - parity[3] ^= binary[3]; + for (uint8_t j = 0; j < 2; j++) { + // append each bit + AppendGraph(false, clock, bs[0 + (4 * j)]); + AppendGraph(false, clock, bs[1 + (4 * j)]); + AppendGraph(false, clock, bs[2 + (4 * j)]); + AppendGraph(false, clock, bs[3 + (4 * j)]); + + // append parity bit + AppendGraph(false, clock, bs[0 + (4 * j)] ^ bs[1 + (4 * j)] ^ bs[2 + (4 * j)] ^ bs[3 + (4 * j)]); + + // keep track of column parity + parity[0] ^= bs[0 + (4 * j)]; + parity[1] ^= bs[1 + (4 * j)]; + parity[2] ^= bs[2 + (4 * j)]; + parity[3] ^= bs[3 + (4 * j)]; + } } - /* parity columns */ + // parity columns AppendGraph(false, clock, parity[0]); AppendGraph(false, clock, parity[1]); AppendGraph(false, clock, parity[2]); AppendGraph(false, clock, parity[3]); - /* stop bit */ + // stop bit AppendGraph(true, clock, 0); } -//by marshmellow //print 64 bit EM410x ID in multiple formats -void printEM410x(uint32_t hi, uint64_t id) { +void printEM410x(uint32_t hi, uint64_t id, bool verbose) { if (!id && !hi) return; - PrintAndLogEx(SUCCESS, "EM410x%s pattern found", (hi) ? " XL" : ""); - uint64_t n = 1; uint64_t id2lo = 0; uint8_t m, i; @@ -195,26 +113,36 @@ void printEM410x(uint32_t hi, uint64_t id) { } } + if (verbose == false) { + + if (hi) { + PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%06X%016" PRIX64), hi, id); + } else { + PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%010" PRIX64), id); + } + return; + } + if (hi) { //output 88 bit em id - PrintAndLogEx(NORMAL, "\nEM TAG ID : "_YELLOW_("%06X%016" PRIX64), hi, id); - PrintAndLogEx(NORMAL, "Clock rate : "_YELLOW_("RF/%d"), g_DemodClock); + PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%06X%016" PRIX64), hi, id); + PrintAndLogEx(SUCCESS, "EM410x XL ( RF/%d )", g_DemodClock); } else { //output 40 bit em id - PrintAndLogEx(NORMAL, "\nEM TAG ID : "_YELLOW_("%010" PRIX64), id); - PrintAndLogEx(NORMAL, "Clock rate : "_YELLOW_("RF/%d"), g_DemodClock); - PrintAndLogEx(NORMAL, "\nPossible de-scramble patterns\n"); - PrintAndLogEx(NORMAL, "Unique TAG ID : %010" PRIX64, id2lo); - PrintAndLogEx(NORMAL, "HoneyWell IdentKey {"); - PrintAndLogEx(NORMAL, "DEZ 8 : %08" PRIu64, id & 0xFFFFFF); - PrintAndLogEx(NORMAL, "DEZ 10 : %010" PRIu64, id & 0xFFFFFFFF); - PrintAndLogEx(NORMAL, "DEZ 5.5 : %05" PRIu64 ".%05" PRIu64, (id >> 16LL) & 0xFFFF, (id & 0xFFFF)); - PrintAndLogEx(NORMAL, "DEZ 3.5A : %03" PRIu64 ".%05" PRIu64, (id >> 32ll), (id & 0xFFFF)); - PrintAndLogEx(NORMAL, "DEZ 3.5B : %03" PRIu64 ".%05" PRIu64, (id & 0xFF000000) >> 24, (id & 0xFFFF)); - PrintAndLogEx(NORMAL, "DEZ 3.5C : %03" PRIu64 ".%05" PRIu64, (id & 0xFF0000) >> 16, (id & 0xFFFF)); - PrintAndLogEx(NORMAL, "DEZ 14/IK2 : %014" PRIu64, id); - PrintAndLogEx(NORMAL, "DEZ 15/IK3 : %015" PRIu64, id2lo); - PrintAndLogEx(NORMAL, "DEZ 20/ZK : %02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64, + PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%010" PRIX64), id); + PrintAndLogEx(SUCCESS, "EM410x ( RF/%d )", g_DemodClock); + PrintAndLogEx(INFO, "-------- " _CYAN_("Possible de-scramble patterns") " ---------"); + PrintAndLogEx(SUCCESS, "Unique TAG ID : %010" PRIX64, id2lo); + PrintAndLogEx(INFO, "HoneyWell IdentKey"); + PrintAndLogEx(SUCCESS, " DEZ 8 : %08" PRIu64, id & 0xFFFFFF); + PrintAndLogEx(SUCCESS, " DEZ 10 : %010" PRIu64, id & 0xFFFFFFFF); + PrintAndLogEx(SUCCESS, " DEZ 5.5 : %05" PRIu64 ".%05" PRIu64, (id >> 16LL) & 0xFFFF, (id & 0xFFFF)); + PrintAndLogEx(SUCCESS, " DEZ 3.5A : %03" PRIu64 ".%05" PRIu64, (id >> 32ll), (id & 0xFFFF)); + PrintAndLogEx(SUCCESS, " DEZ 3.5B : %03" PRIu64 ".%05" PRIu64, (id & 0xFF000000) >> 24, (id & 0xFFFF)); + PrintAndLogEx(SUCCESS, " DEZ 3.5C : %03" PRIu64 ".%05" PRIu64, (id & 0xFF0000) >> 16, (id & 0xFFFF)); + PrintAndLogEx(SUCCESS, " DEZ 14/IK2 : %014" PRIu64, id); + PrintAndLogEx(SUCCESS, " DEZ 15/IK3 : %015" PRIu64, id2lo); + PrintAndLogEx(SUCCESS, " DEZ 20/ZK : %02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64 "%02" PRIu64, (id2lo & 0xf000000000) >> 36, (id2lo & 0x0f00000000) >> 32, (id2lo & 0x00f0000000) >> 28, @@ -226,9 +154,11 @@ void printEM410x(uint32_t hi, uint64_t id) { (id2lo & 0x00000000f0) >> 4, (id2lo & 0x000000000f) ); + PrintAndLogEx(INFO, ""); + uint64_t paxton = (((id >> 32) << 24) | (id & 0xffffff)) + 0x143e00; - PrintAndLogEx(NORMAL, "}\nOther : %05" PRIu64 "_%03" PRIu64 "_%08" PRIu64, (id & 0xFFFF), ((id >> 16LL) & 0xFF), (id & 0xFFFFFF)); - PrintAndLogEx(NORMAL, "Pattern Paxton : %" PRIu64 " [0x%" PRIX64 "]", paxton, paxton); + PrintAndLogEx(SUCCESS, "Other : %05" PRIu64 "_%03" PRIu64 "_%08" PRIu64, (id & 0xFFFF), ((id >> 16LL) & 0xFF), (id & 0xFFFFFF)); + PrintAndLogEx(SUCCESS, "Pattern Paxton : %" PRIu64 " [0x%" PRIX64 "]", paxton, paxton); uint32_t p1id = (id & 0xFFFFFF); uint8_t arr[32] = {0x00}; @@ -268,12 +198,13 @@ void printEM410x(uint32_t hi, uint64_t id) { p1 |= arr[2] << 4; p1 |= arr[1] << 5; p1 |= arr[0] << 9; - PrintAndLogEx(NORMAL, "Pattern 1 : %d [0x%X]", p1, p1); + PrintAndLogEx(SUCCESS, "Pattern 1 : %d [0x%X]", p1, p1); uint16_t sebury1 = id & 0xFFFF; uint8_t sebury2 = (id >> 16) & 0x7F; uint32_t sebury3 = id & 0x7FFFFF; - PrintAndLogEx(NORMAL, "Pattern Sebury : %d %d %d [0x%X 0x%X 0x%X]", sebury1, sebury2, sebury3, sebury1, sebury2, sebury3); + PrintAndLogEx(SUCCESS, "Pattern Sebury : %d %d %d [0x%X 0x%X 0x%X]", sebury1, sebury2, sebury3, sebury1, sebury2, sebury3); + PrintAndLogEx(INFO, "------------------------------------------------"); } } /* Read the ID of an EM410x tag. @@ -321,9 +252,8 @@ int AskEm410xDecode(bool verbose, uint32_t *hi, uint64_t *lo) { printDemodBuff(0, false, false, true); } - if (verbose) - printEM410x(*hi, *lo); - + printEM410x(*hi, *lo, verbose); + g_em410xid = *lo; return PM3_SUCCESS; } @@ -342,8 +272,19 @@ int AskEm410xDemod(int clk, int invert, int maxErr, size_t maxLen, bool amplify, // this read loops on device side. // uses the demod in lfops.c static int CmdEM410xWatch(const char *Cmd) { - uint8_t c = tolower(param_getchar(Cmd, 0)); - if (c == 'h') return usage_lf_em410x_watch(); + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x watch", + "Enables Electro Marine (EM) compatible reader mode printing details of scanned tags.\n" + "Run until the button is pressed or another USB command is issued.", + "lf em 410x watch" + ); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); PrintAndLogEx(SUCCESS, "Watching for EM410x cards - place tag on antenna"); PrintAndLogEx(INFO, "Press pm3-button to stop reading cards"); @@ -367,111 +308,189 @@ int demodEM410x(bool verbose) { } static int CmdEM410xDemod(const char *Cmd) { - char cmdp = tolower(param_getchar(Cmd, 0)); - if (strlen(Cmd) > 10 || cmdp == 'h') return usage_lf_em410x_demod(); + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x demod", + "Try to find EM 410x preamble, if found decode / descramble data", + "lf em 410x demod -> demod an EM410x Tag ID from GraphBuffer\n" + "lf em 410x demod --clk 32 -> demod an EM410x Tag ID from GraphBuffer using a clock of RF/32\n" + "lf em 410x demod --clk 32 -i -> demod an EM410x Tag ID from GraphBuffer using a clock of RF/32 and inverting data\n" + "lf em 410x demod -i -> demod an EM410x Tag ID from GraphBuffer while inverting data\n" + "lf em 410x demod --clk 64 -i --err 0 -> demod an EM410x Tag ID from GraphBuffer using a clock of RF/64 and inverting data and allowing 0 demod errors" + ); + + void *argtable[] = { + arg_param_begin, + arg_u64_0(NULL, "clk", "", "optional - clock (default autodetect)"), + arg_u64_0(NULL, "err", "", "optional - maximum allowed errors (default 100)"), + arg_u64_0(NULL, "len", "", "optional - maximum length"), + arg_lit0("i", "invert", "optional - invert output"), + arg_lit0("a", "amp", "optional - amplify signal"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int clk = arg_get_u32_def(ctx, 1, 0); + int max_err = arg_get_u32_def(ctx, 2, 100); + size_t max_len = arg_get_u32_def(ctx, 3, 0); + bool invert = arg_get_lit(ctx, 4); + bool amplify = arg_get_lit(ctx, 5); + CLIParserFree(ctx); uint32_t hi = 0; uint64_t lo = 0; - int clk = 0; - int invert = 0; - int maxErr = 100; - size_t maxLen = 0; - char amp = tolower(param_getchar(Cmd, 0)); - sscanf(Cmd, "%i %i %i %zu %c", &clk, &invert, &maxErr, &maxLen, &); - bool amplify = amp == 'a'; - if (AskEm410xDemod(clk, invert, maxErr, maxLen, amplify, &hi, &lo, true) != PM3_SUCCESS) + if (AskEm410xDemod(clk, invert, max_err, max_len, amplify, &hi, &lo, true) != PM3_SUCCESS) return PM3_ESOFT; - g_em410xid = lo; return PM3_SUCCESS; } // this read is the "normal" read, which download lf signal and tries to demod here. -static int CmdEM410xRead(const char *Cmd) { - char cmdp = tolower(param_getchar(Cmd, 0)); - if (strlen(Cmd) > 10 || cmdp == 'h') return usage_lf_em410x_demod(); +static int CmdEM410xReader(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x reader", + "read EM 410x tag", + "lf em 410x reader -> reader\n" + "lf em 410x reader -@ -> continuous reader mode\n" + "lf em 410x reader --clk 32 -> reader using a clock of RF/32\n" + "lf em 410x reader --clk 32 -i -> reader using a clock of RF/32 and inverting data\n" + "lf em 410x reader -i -> reader while inverting data\n" + "lf em 410x reader --clk 64 -i --err 0 -> reader using a clock of RF/64 and inverting data and allowing 0 demod errors" + ); - uint32_t hi = 0; - uint64_t lo = 0; - int clk = 0; - int invert = 0; - int maxErr = 100; - size_t maxLen = 0; - char amp = tolower(param_getchar(Cmd, 0)); - sscanf(Cmd, "%i %i %i %zu %c", &clk, &invert, &maxErr, &maxLen, &); - bool amplify = amp == 'a'; - lf_read(false, 12288); - return AskEm410xDemod(clk, invert, maxErr, maxLen, amplify, &hi, &lo, true); + void *argtable[] = { + arg_param_begin, + arg_u64_0(NULL, "clk", "", "optional - clock (default autodetect)"), + arg_u64_0(NULL, "err", "", "optional - maximum allowed errors (default 100)"), + arg_u64_0(NULL, "len", "", "optional - maximum length"), + arg_lit0("i", "invert", "optional - invert output"), + arg_lit0("a", "amp", "optional - amplify signal"), + arg_lit0("@", NULL, "optional - continuous reader mode"), + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int clk = arg_get_u32_def(ctx, 1, 0); + int max_err = arg_get_u32_def(ctx, 2, 100); + size_t max_len = arg_get_u32_def(ctx, 3, 0); + bool invert = arg_get_lit(ctx, 4); + bool amplify = arg_get_lit(ctx, 5); + bool cm = arg_get_lit(ctx, 6); + bool verbose = arg_get_lit(ctx, 7); + CLIParserFree(ctx); + + if (cm) { + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + } + + do { + uint32_t hi = 0; + uint64_t lo = 0; + lf_read(false, 12288); + AskEm410xDemod(clk, invert, max_err, max_len, amplify, &hi, &lo, verbose); + } while (cm && !kbd_enter_pressed()); + + return PM3_SUCCESS; } // emulate an EM410X tag static int CmdEM410xSim(const char *Cmd) { - char cmdp = tolower(param_getchar(Cmd, 0)); - if (cmdp == 'h') return usage_lf_em410x_sim(); + + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x sim", + "Enables simulation of EM 410x card.\n" + "Simulation runs until the button is pressed or another USB command is issued.", + "lf em 410x sim --id 0F0368568B\n" + "lf em 410x sim --id 0F0368568B --clk 32" + ); - uint8_t uid[5] = {0x00}; + void *argtable[] = { + arg_param_begin, + arg_u64_0(NULL, "clk", "", "optional - clock [32|64] (default 64)"), + arg_str1("i", "id", "", "ID number (5 hex bytes)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); - /* clock is 64 in EM410x tags */ - uint8_t clk = 64; + // clock is 64 in EM410x tags + int clk = arg_get_u32_def(ctx, 1, 64); + int uid_len = 0; + uint8_t uid[5] = {0}; + CLIGetHexWithReturn(ctx, 2, uid, &uid_len); + CLIParserFree(ctx); - if (param_gethex(Cmd, 0, uid, 10)) { - PrintAndLogEx(FAILED, "UID must include 10 HEX symbols"); + if (uid_len != 5) { + PrintAndLogEx(FAILED, "UID must include 5 hex bytes (%u)", uid_len); return PM3_EINVARG; } - param_getdec(Cmd, 1, &clk); - - PrintAndLogEx(SUCCESS, "Starting simulating UID "_YELLOW_("%02X%02X%02X%02X%02X")" clock: "_YELLOW_("%d"), uid[0], uid[1], uid[2], uid[3], uid[4], clk); + PrintAndLogEx(SUCCESS, "Starting simulating UID "_YELLOW_("%s")" clock: "_YELLOW_("%d"), sprint_hex_inrow(uid, sizeof(uid)), clk); PrintAndLogEx(SUCCESS, "Press pm3-button to abort simulation"); - ConstructEM410xEmulGraph(Cmd, clk); + em410x_construct_emul_graph(uid, clk); - CmdLFSim("0"); //240 start_gap. + CmdLFSim("0"); // 240 start_gap. return PM3_SUCCESS; } -static int CmdEM410xBrute(const char *Cmd) { +static int CmdEM410xBrute(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x brute", + "bruteforcing by emulating EM 410x tag", + "lf em 410x brute -f ids.txt\n" + "lf em 410x brute -f ids.txt --clk 32\n" + "lf em 410x brute -f ids.txt --delay 3000\n" + "lf em 410x brute -f ids.txt --delay 3000 --clk 32\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_u64_1(NULL, "clk", "", "optional - clock [32|64] (default 64)"), + arg_u64_1(NULL, "delay", "", "optional - pause delay in milliseconds between UIDs simulation (default 1000ms)"), + arg_str1("f", "file", "", "file with UIDs in HEX format, one per line"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + // clock default 64 in EM410x + uint32_t clk = arg_get_u32_def(ctx, 1, 64); + + // default pause time: 1 second + uint32_t delay = arg_get_u32_def(ctx, 2, 1000); + + int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; - FILE *f = NULL; - char buf[11]; - uint32_t uidcnt = 0; - uint8_t stUidBlock = 20; - uint8_t *uidBlock = NULL, *p = NULL; - uint8_t uid[5] = {0x00}; - /* clock is 64 in EM410x tags */ - uint8_t clock1 = 64; - /* default pause time: 1 second */ - uint32_t delay = 1000; + CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); - char cmdp = tolower(param_getchar(Cmd, 0)); - if (cmdp == 'h') return usage_lf_em410x_brute(); - - cmdp = tolower(param_getchar(Cmd, 1)); - if (cmdp == 'd') { - delay = param_get32ex(Cmd, 2, 1000, 10); - param_getdec(Cmd, 4, &clock1); - } else if (cmdp == 'c') { - param_getdec(Cmd, 2, &clock1); - delay = param_get32ex(Cmd, 4, 1000, 10); - } - - int filelen = param_getstr(Cmd, 0, filename, FILE_PATH_SIZE); - if (filelen == 0) { + if (fnlen == 0) { PrintAndLogEx(ERR, "Error: Please specify a filename"); return PM3_EINVARG; } + + uint32_t uidcnt = 0; + uint8_t stUidBlock = 20; + uint8_t *p = NULL; + uint8_t uid[5] = {0x00}; + // open file + FILE *f = NULL; if ((f = fopen(filename, "r")) == NULL) { PrintAndLogEx(ERR, "Error: Could not open UIDs file ["_YELLOW_("%s")"]", filename); return PM3_EFILE; } - uidBlock = calloc(stUidBlock, 5); - if (uidBlock == NULL) { + // allocate mem for file contents + uint8_t *uidblock = calloc(stUidBlock, 5); + if (uidblock == NULL) { fclose(f); - return PM3_ESOFT; + PrintAndLogEx(ERR, "Error: can't allocate memory"); + return PM3_EMALLOC; } + // read file into memory + char buf[11]; + while (fgets(buf, sizeof(buf), f)) { if (strlen(buf) < 10 || buf[9] == '\n') continue; while (fgetc(f) != '\n' && !feof(f)); //goto next line @@ -481,7 +500,7 @@ static int CmdEM410xBrute(const char *Cmd) { if (param_gethex(buf, 0, uid, 10)) { PrintAndLogEx(FAILED, "UIDs must include 10 HEX symbols"); - free(uidBlock); + free(uidblock); fclose(f); return PM3_ESOFT; } @@ -489,109 +508,118 @@ static int CmdEM410xBrute(const char *Cmd) { buf[10] = 0; if (stUidBlock - uidcnt < 2) { - p = realloc(uidBlock, 5 * (stUidBlock += 10)); + p = realloc(uidblock, 5 * (stUidBlock += 10)); if (!p) { PrintAndLogEx(WARNING, "Cannot allocate memory for UIDs"); - free(uidBlock); + free(uidblock); fclose(f); return PM3_ESOFT; } - uidBlock = p; + uidblock = p; } - memset(uidBlock + 5 * uidcnt, 0, 5); - num_to_bytes(strtoll(buf, NULL, 16), 5, uidBlock + 5 * uidcnt); + memset(uidblock + 5 * uidcnt, 0, 5); + num_to_bytes(strtoll(buf, NULL, 16), 5, uidblock + 5 * uidcnt); uidcnt++; memset(buf, 0, sizeof(buf)); } - fclose(f); if (uidcnt == 0) { PrintAndLogEx(FAILED, "No UIDs found in file"); - free(uidBlock); + free(uidblock); return PM3_ESOFT; } PrintAndLogEx(SUCCESS, "Loaded "_YELLOW_("%d")" UIDs from "_YELLOW_("%s")", pause delay:"_YELLOW_("%d")" ms", uidcnt, filename, delay); // loop + uint8_t testuid[5]; for (uint32_t c = 0; c < uidcnt; ++c) { - char testuid[11]; - testuid[10] = 0; - if (kbd_enter_pressed()) { PrintAndLogEx(WARNING, "\nAborted via keyboard!\n"); - free(uidBlock); + free(uidblock); return PM3_EOPABORTED; } - sprintf(testuid, "%010" PRIX64, bytes_to_num(uidBlock + 5 * c, 5)); - PrintAndLogEx(NORMAL, "Bruteforce %d / %d: simulating UID %s, clock %d", c + 1, uidcnt, testuid, clock1); + memcpy(testuid, uidblock + 5 * c, 5); + PrintAndLogEx(INFO, "Bruteforce %d / %d: simulating UID " _YELLOW_("%s") + , c + 1 + , uidcnt + , sprint_hex_inrow(testuid, sizeof(testuid)) + ); - ConstructEM410xEmulGraph(testuid, clock1); + em410x_construct_emul_graph(testuid, clk); CmdLFSim("0"); //240 start_gap. msleep(delay); } - - free(uidBlock); + free(uidblock); return PM3_SUCCESS; } //currently only supports manchester modulations -static int CmdEM410xWatchnSpoof(const char *Cmd) { +static int CmdEM410xSpoof(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x spoof", + "Watch 'nd Spoof, activates reader\n" + "Waits until a EM 410x tag gets presented then Proxmark3 starts simulating the found UID", + "lf em 410x spoof" + ); - char cmdp = tolower(param_getchar(Cmd, 0)); - if (cmdp == 'h') return usage_lf_em410x_ws(); + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); // loops if the captured ID was in XL-format. - CmdEM410xWatch(Cmd); + CmdEM410xReader("-@"); PrintAndLogEx(SUCCESS, "# Replaying captured ID: "_YELLOW_("%010" PRIx64), g_em410xid); CmdLFaskSim(""); return PM3_SUCCESS; } static int CmdEM410xClone(const char *Cmd) { - char cmdp = tolower(param_getchar(Cmd, 0)); - if (cmdp == 0x00 || cmdp == 'h') return usage_lf_em410x_clone(); + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 410x clone", + "Writes EM410x ID to a T55x7 or Q5/T5555 tag", + "lf em 410x clone --id 0F0368568B -> write id to T55x7 tag\n" + "lf em 410x clone --id 0F0368568B --q5 -> write id to Q5/T5555 tag" + ); - uint64_t id = param_get64ex(Cmd, 0, -1, 16); - uint8_t card = param_get8ex(Cmd, 1, 0xFF, 10); - uint8_t clock1 = param_get8ex(Cmd, 2, 0, 10); + void *argtable[] = { + arg_param_begin, + arg_u64_0(NULL, "clk", "", "optional - clock <16|32|40|64> (default 64)"), + arg_str1("u", "uid", "", "ID number (5 hex bytes)"), + arg_lit0(NULL, "q5", "optional - specify writing to Q5/T5555 tag"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); - // Check ID - if (id == 0xFFFFFFFFFFFFFFFF) { - PrintAndLogEx(ERR, "error, ID is required\n"); - usage_lf_em410x_clone(); - return PM3_EINVARG; - } - if (id >= 0x10000000000) { - PrintAndLogEx(ERR, "error, given EM410x ID is longer than 40 bits\n"); - usage_lf_em410x_clone(); - return PM3_EINVARG; - } + // clock default 64 in EM410x + uint32_t clk = arg_get_u32_def(ctx, 1, 64); + int uid_len = 0; + uint8_t uid[5] = {0}; + CLIGetHexWithReturn(ctx, 2, uid, &uid_len); + bool q5 = arg_get_lit(ctx, 3); + CLIParserFree(ctx); - // Check Card - if (card > 1) { - PrintAndLogEx(FAILED, "error, bad card type selected\n"); - usage_lf_em410x_clone(); - return PM3_EINVARG; - } - - // Check Clock - if (clock1 == 0) - clock1 = 64; + uint64_t id = bytes_to_num(uid, uid_len); // Allowed clock rates: 16, 32, 40 and 64 - if ((clock1 != 16) && (clock1 != 32) && (clock1 != 64) && (clock1 != 40)) { - PrintAndLogEx(FAILED, "error, clock rate" _RED_("%d")" not valid", clock1); - PrintAndLogEx(INFO, "supported clock rates: " _YELLOW_("16, 32, 40, 60") "\n"); - usage_lf_em410x_clone(); + if ((clk != 16) && (clk != 32) && (clk != 64) && (clk != 40)) { + PrintAndLogEx(FAILED, "supported clock rates are " _YELLOW_("16, 32, 40, 64") " got " _RED_("%d") "\n", clk); return PM3_EINVARG; } - PrintAndLogEx(SUCCESS, "Writing " _YELLOW_("%s") " tag with UID 0x%010" PRIx64 " (clock rate: %d)", (card == 1) ? "T55x7" : "Q5/T5555", id, clock1); + char cardtype[16] = {"T55x7"}; + if (q5) { + snprintf(cardtype, sizeof(cardtype), "Q5/T5555"); + } + + PrintAndLogEx(SUCCESS, "Preparing to clone EM4102 to " _YELLOW_("%s") " tag with ID " _GREEN_("%010" PRIX64) " (RF/%d)", cardtype, id, clk); // NOTE: We really should pass the clock in as a separate argument, but to // provide for backwards-compatibility for older firmware, and to avoid // having to add another argument to CMD_LF_EM410X_WRITE, we just store @@ -604,8 +632,8 @@ static int CmdEM410xClone(const char *Cmd) { uint32_t low; } PACKED params; - params.card = card; - params.clock = clock1; + params.card = (q5) ? 0 : 1; + params.clock = clk; params.high = (uint32_t)(id >> 32); params.low = (uint32_t)id; @@ -617,7 +645,7 @@ static int CmdEM410xClone(const char *Cmd) { switch (resp.status) { case PM3_SUCCESS: { PrintAndLogEx(SUCCESS, "Done"); - PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf em 410x_read`") " to verify"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf em 410x reader`") " to verify"); break; } default: { @@ -629,15 +657,15 @@ static int CmdEM410xClone(const char *Cmd) { } static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - //{"demod", CmdEMdemodASK, IfPm3Lf, "Extract ID from EM410x tag on antenna)"}, - {"demod", CmdEM410xDemod, AlwaysAvailable, "demodulate a EM410x tag from the GraphBuffer"}, - {"read", CmdEM410xRead, IfPm3Lf, "attempt to read and extract tag data"}, - {"sim", CmdEM410xSim, IfPm3Lf, "simulate EM410x tag"}, - {"brute", CmdEM410xBrute, IfPm3Lf, "reader bruteforce attack by simulating EM410x tags"}, - {"watch", CmdEM410xWatch, IfPm3Lf, "watches for EM410x 125/134 kHz tags (option 'h' for 134)"}, - {"spoof", CmdEM410xWatchnSpoof, IfPm3Lf, "watches for EM410x 125/134 kHz tags, and replays them. (option 'h' for 134)" }, - {"clone", CmdEM410xClone, IfPm3Lf, "write EM410x UID to T55x7 or Q5/T5555 tag"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + //{"demod", CmdEMdemodASK, IfPm3Lf, "Extract ID from EM410x tag on antenna)"}, + {"demod", CmdEM410xDemod, AlwaysAvailable, "demodulate a EM410x tag from the GraphBuffer"}, + {"reader", CmdEM410xReader, IfPm3Lf, "attempt to read and extract tag data"}, + {"sim", CmdEM410xSim, IfPm3Lf, "simulate EM410x tag"}, + {"brute", CmdEM410xBrute, IfPm3Lf, "reader bruteforce attack by simulating EM410x tags"}, + {"watch", CmdEM410xWatch, IfPm3Lf, "watches for EM410x 125/134 kHz tags (option 'h' for 134)"}, + {"spoof", CmdEM410xSpoof, IfPm3Lf, "watches for EM410x 125/134 kHz tags, and replays them. (option 'h' for 134)" }, + {"clone", CmdEM410xClone, IfPm3Lf, "write EM410x UID to T55x7 or Q5/T5555 tag"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdlfem410x.h b/client/src/cmdlfem410x.h index 62d35cf55..d31812b56 100644 --- a/client/src/cmdlfem410x.h +++ b/client/src/cmdlfem410x.h @@ -16,7 +16,7 @@ int CmdLFEM410X(const char *Cmd); int demodEM410x(bool verbose); -void printEM410x(uint32_t hi, uint64_t id); +void printEM410x(uint32_t hi, uint64_t id, bool verbose); int AskEm410xDecode(bool verbose, uint32_t *hi, uint64_t *lo); int AskEm410xDemod(int clk, int invert, int maxErr, size_t maxLen, bool amplify, uint32_t *hi, uint64_t *lo, bool verbose); diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index be0b17bb1..ccdf30394 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -10,152 +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 *byte, int fwr, int lwr, em4x50_word_t *words) { - - // 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}; +static void prepare_result(const uint8_t *data, int fwr, int lwr, em4x50_word_t *words) { + // restructure received result in "em4x50_word_t" structure 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; + words[i].byte[j] = data[i * 4 + (3 - j)]; } - - // 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; } } @@ -207,7 +81,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,271 +125,370 @@ 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) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 eload", + "Loads EM4x50 tag dump into emulator memory on device.", + "lf em 4x50 eload -f mydump.bin\n" + "lf em 4x50 eload -f mydump.eml\n" + "lf em 4x50 eload -f mydump.json" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("f", "filename", "", "dump filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + // read data from dump file; file type has to be "bin", "eml" or "json" + size_t bytes_read = 0; + uint8_t data[DUMP_FILESIZE] = {0x0}; + + 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) { + 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\n" + "lf em 4x50 esave -f mydump.eml\n" + "lf em 4x50 esave -f mydump.json\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "data filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + // download emulator memory + PrintAndLogEx(SUCCESS, "Reading emulator memory..."); + uint8_t data[DUMP_FILESIZE] = {0x0}; + if (GetFromDevice(BIG_BUF_EML, data, DUMP_FILESIZE, 0, NULL, 0, NULL, 2500, false) == false) { + PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); + return PM3_ETIMEOUT; + } + + // valid em4x50 data? + uint32_t serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); + uint32_t 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 (fnlen == 0) { + PrintAndLogEx(INFO, "Using UID as filename"); + char *fptr = 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) { + 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); + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); + CLIParserFree(ctx); + + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes"); + return PM3_EINVARG; + } + + uint32_t password = BYTES2UINT32(pwd); + + // start + clearCommandBuffer(); + PacketResponseNG resp; + 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) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 brute", + "Tries to bruteforce the password of a EM4x50.\n" + "Function can be stopped by pressing pm3 button.", + "lf em 4x50 brute --first 12330000 --last 12340000 -> tries pwds from 0x12330000 to 0x1234000000\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1(NULL, "first", "", "first password (start), 4 bytes, lsb"), + arg_str1(NULL, "last", "", "last password (stop), 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int first_len = 0; + uint8_t first[4] = {0,0,0,0}; + CLIGetHexWithReturn(ctx, 1, first, &first_len); + int last_len = 0; + uint8_t last[4] = {0,0,0,0}; + CLIGetHexWithReturn(ctx, 2, last, &last_len); + CLIParserFree(ctx); + + if (first_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes"); + return PM3_EINVARG; + } + if (last_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes"); + return PM3_EINVARG; + } + + em4x50_data_t etd; + etd.password1 = BYTES2UINT32(first); + etd.password2 = BYTES2UINT32(last); + + // 27 passwords/second (empirical value) + const int speed = 27; + + // print some information + int no_iter = etd.password2 - etd.password1 + 1; + int dur_s = no_iter / speed; + int dur_h = dur_s / 3600; + int 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(); + PacketResponseNG resp; + SendCommandNG(CMD_LF_EM4X50_BRUTE, (uint8_t *)&etd, sizeof(etd)); + WaitForResponse(CMD_LF_EM4X50_BRUTE, &resp); + + // print response + if (resp.status == PM3_SUCCESS) + PrintAndLogEx(SUCCESS, "Password " _GREEN_("found") ": 0x%08x", resp.data.asDwords[0]); + else + PrintAndLogEx(FAILED, "Password: " _RED_("not found")); + + return PM3_SUCCESS; +} + +// upload passwords from given dictionary to device and start check; +// if no filename is given dictionary "t55xx_default_pwds.dic" is used +int CmdEM4x50Chk(const char *Cmd) { + 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" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "dictionary filename"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParserFree(ctx); + + if (IfPm3Flash() == false) { + PrintAndLogEx(WARNING, "no flash memory available"); + return PM3_EFLASH; + } + + // no filename -> default = t55xx_default_pwds + if (strlen(filename) == 0) { + snprintf(filename, sizeof(filename), "t55xx_default_pwds"); + PrintAndLogEx(INFO, "treating file as T55xx keys"); + } + + size_t datalen = 0; + uint8_t data[FLASH_MEM_MAX_SIZE] = {0x0}; + uint8_t *keys = data; + uint32_t key_count = 0; + + int 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"); + + int status = PM3_EFAILED; + int keyblock = 2000; // block with 2000 bytes -> 500 keys + uint8_t destfn[32] = "em4x50_chk.bin"; + + PacketResponseNG resp; + int 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; + } + + PrintAndLogEx(NORMAL, ""); + + // print response + if (status == PM3_SUCCESS) { + 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(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) { - - // envoke reading - // - without option -> standard read mode - // - with given address (option a) (and optional password if address is - // read protected) -> selective read mode +// envoke reading +// - with given address (option b) (and optional password if address is +// read protected) -> selective read mode +int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out) { em4x50_data_t edata = { .pwd_given = false, .addr_given = false }; @@ -532,145 +505,226 @@ 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) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 read", + "Reads single EM4x50 block/word.", + "lf em 4x50 rdbl -b 3\n" + "lf em 4x50 rdbl -b 32 -p 12345678 -> reads block 32 with pwd 0x12345678\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "block/word address"), + arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int addr = arg_get_int_def(ctx, 1, 0); + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 2, pwd, &pwd_len); + CLIParserFree(ctx); + + if (addr <= 0 || addr >= EM4X50_NO_WORDS) { + return PM3_EINVARG; + } 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; + etd.addresses = (addr << 8) | addr; + etd.addr_given = true; - 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_read(); - } - case 'a': { - param_getdec(Cmd, cmdp + 1, &etd.address); - - // 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; - } + if (pwd_len) { + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + 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(); - - return em4x50_read(&etd, NULL, true); + return em4x50_read(&etd, NULL); } -int CmdEM4x50Dump(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 CmdEM4x50Info(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 info", + "Tag information EM4x50.", + "lf em 4x50 info\n" + "lf em 4x50 info -p 12345678 -> uses pwd 0x12345678\n" + ); - em4x50_data_t etd; - etd.pwd_given = false; - etd.addr_given = false; - etd.newpwd_given = false; + void *argtable[] = { + arg_param_begin, + arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), + arg_param_end + }; - char filename[FILE_PATH_SIZE] = {0x00}; - char *fptr = filename; + CLIExecWithReturn(ctx, Cmd, argtable, true); + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); + CLIParserFree(ctx); - 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; - }; + em4x50_data_t etd = {.pwd_given = false}; + if (pwd_len) { + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + } } - // validation - if (errors) - return usage_lf_em4x50_dump(); - - 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")); + if ( resp.status == PM3_SUCCESS) + print_info_result(resp.data.asBytes); + else + PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); + + return resp.status; +} + +int CmdEM4x50Reader(const char *Cmd) { + 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 { + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_READER, 0, 0); + WaitForResponseTimeoutW(CMD_LF_EM4X50_READER, &resp, -1, false); + + // iceman, misuse of return status code. + int now = resp.status; + + 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]; + memset(r, 0, sizeof(r)); + 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) { + 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\n" + "lf em 4x50 dump -f mydump.eml\n" + "lf em 4x50 dump -p 12345678\n" + "lf em 4x50 dump -f mydump.eml -p 12345678" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), + arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int fnLen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnLen); + + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 2, pwd, &pwd_len); + CLIParserFree(ctx); + + em4x50_data_t etd = {.pwd_given = false}; + + if (pwd_len) { + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes"); + CLIParserFree(ctx); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + } + } + + 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."); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); return PM3_ESOFT; } @@ -678,91 +732,399 @@ 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"); + char *fptr = 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}; + uint8_t data[DUMP_FILESIZE] = {0}; 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. - saveFileEML(filename, data, sizeof(data), 4); saveFile(filename, ".bin", data, sizeof(data)); - //saveFileJSON... + saveFileEML(filename, data, sizeof(data), 4); + saveFileJSON(filename, jsfEM4x50, data, sizeof(data), NULL); return PM3_SUCCESS; } -int CmdEM4x50Wipe(const char *Cmd) { +// envoke writing a single word (32 bit) to a EM4x50 tag +int CmdEM4x50Write(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 wrbl", + "Writes single block/word to EM4x50 tag.", + "lf em 4x50 wrbl -b 3 -d 4f22e7ff \n" + "lf em 4x50 wrbl -b 3 -d 4f22e7ff -p 12345678\n" + ); - // fills EM4x50 tag with zeros including password + void *argtable[] = { + arg_param_begin, + arg_int1("b", "block", "", "block/word address, dec"), + arg_str1("d", "data", "", "data, 4 bytes, lsb"), + arg_str0("p", "pwd", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); - bool errors = false, bpwd = false; - uint8_t cmdp = 0; - em4x50_data_t etd; - PacketResponseNG resp; + int addr = arg_get_int_def(ctx, 1, 0); + + int word_len = 0; + uint8_t word[4] = {0x0}; + CLIGetHexWithReturn(ctx, 2, word, &word_len); - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 3, pwd, &pwd_len); + CLIParserFree(ctx); + + if (addr <= 0 || addr >= EM4X50_NO_WORDS) { + PrintAndLogEx(FAILED, "address has to be within range [0, 31]"); + return PM3_EINVARG; + } + + if (word_len != 4) { + PrintAndLogEx(FAILED, "word/data length must be 4 bytes instead of %d", word_len); + return PM3_EINVARG; + } - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_lf_em4x50_wipe(); - - 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; + em4x50_data_t etd = {.pwd_given = false}; + if (pwd_len) { + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; } } - if (errors || !bpwd) - return usage_lf_em4x50_wipe(); + etd.addresses = (addr << 8) | addr; + etd.addr_given = true; + etd.word = BYTES2UINT32(word); 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_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; } - // print response - bool isOK = resp.status; - if (isOK) { - PrintAndLogEx(SUCCESS, "\nwiping data " _GREEN_("ok") "\n"); - } else { - PrintAndLogEx(FAILED, "\nwiping data " _RED_("failed") "\n"); + int status = resp.status; + if (status == PM3_ETEAROFF) + return status; + + 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 rdbl -a %u") "` - to read your data", addr); + return PM3_SUCCESS; } +// envokes changing the password of EM4x50 tag +int CmdEM4x50WritePwd(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 writepwd", + "Writes EM4x50 password.", + "lf em 4x50 writepwd -p 4f22e7ff -n 12345678" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("p", "pwd", "", "password, 4 hex bytes, lsb"), + arg_str1("n", "new", "", "new password, 4 hex bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); + + int npwd_len = 0; + uint8_t npwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 2, npwd, &npwd_len); + + CLIParserFree(ctx); + + em4x50_data_t etd; + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + return PM3_EINVARG; + } else { + etd.password1 = BYTES2UINT32(pwd); + } + + if (npwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", npwd_len); + return PM3_EINVARG; + } else { + etd.password2 = BYTES2UINT32(npwd); + } + + PacketResponseNG resp; + 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; + } + + if (resp.status == PM3_ETEAROFF) + return PM3_SUCCESS; + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Writing password (" _RED_("failed") ")"); + return PM3_EFAILED; + } + + PrintAndLogEx(SUCCESS, "Writing new password %s (%s)" + , sprint_hex_inrow(npwd, sizeof(npwd)) + , _GREEN_("ok") + ); + return PM3_SUCCESS; +} + +// fills EM4x50 tag with zeros including password +int CmdEM4x50Wipe(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 wipe", + "Wipes EM4x50 tag by filling it with zeros, including the new password\n" + "Must give a password.", + "lf em 4x50 wipe -p 12345678" + ); + + void *argtable[] = { + arg_param_begin, + arg_str1("p", "passsword", "", "password, 4 bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); + CLIParserFree(ctx); + + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + CLIParserFree(ctx); + return PM3_EINVARG; + } + + em4x50_data_t etd = {.pwd_given = false, .word = 0x0, .password2 = 0x0}; + + etd.password1 = BYTES2UINT32(pwd); + etd.pwd_given = true; + + // clear password + PacketResponseNG resp; + 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; + } + + if (resp.status == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Resetting password to 00000000 (" _GREEN_("ok") ")"); + } else { + 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; + } + + if ( resp.status != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "Wiping data " _RED_("failed")); + return PM3_ESOFT; + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Done"); + return PM3_SUCCESS; +} + +int CmdEM4x50Restore(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 restore", + "Restores data from dumpfile onto a Em4x50 tag.\n" + "if used with -u, the filetemplate `lf-4x50-UID-dump.bin` is used as filename", + "lf em 4x50 restore -u 1b5aff5c -> uses lf-4x50-1B5AFF5C-dump.bin\n" + "lf em 4x50 restore -f 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 hex bytes, msb"), + arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), + arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int uidLen = 0; + uint8_t uid[4] = {0x0}; + CLIGetHexWithReturn(ctx, 1, uid, &uidLen); + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + + int pwd_len = 0; + uint8_t pwd[4] = {0x0}; + CLIGetHexWithReturn(ctx, 3, pwd, &pwd_len); + CLIParserFree(ctx); + + if ((uidLen && fnlen) || (!uidLen && !fnlen)) { + PrintAndLogEx(FAILED, "either use option 'u' or option 'f'"); + return PM3_EINVARG; + } + + int startblock = EM4X50_CONTROL + 1; + em4x50_data_t etd = {.pwd_given = false}; + + if (pwd_len) { + if (pwd_len != 4) { + PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); + 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; + } + } + + if (uidLen) { + PrintAndLogEx(INFO, "Using UID as filename"); + char *fptr = filename; + fptr += sprintf(fptr, "lf-4x50-"); + FillFileNameByUID(fptr, uid, "-dump", 4); + } + + PrintAndLogEx(INFO, "Restoring " _YELLOW_("%s")" to card", filename); + + // read data from dump file; file type has to be "bin", "eml" or "json" + uint8_t data[DUMP_FILESIZE] = {0x0}; + size_t bytes_read = 0; + 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))); + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); + if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT)) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(FAILED, "Restoring data " _RED_("failed")); + return PM3_ESOFT; + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Done"); + return PM3_SUCCESS; +} + +int CmdEM4x50Sim(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "lf em 4x50 sim", + "Simulates a EM4x50 tag.\n" + "Upload using `lf em 4x50 eload`", + "lf em 4x50 sim" + ); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + + PrintAndLogEx(INFO, "Simulating data from emulator memory"); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X50_SIM, NULL, 0); + PacketResponseNG resp; + WaitForResponse(CMD_LF_EM4X50_SIM, &resp); + if (resp.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"}, + {"brute", CmdEM4x50Brute, IfPm3EM4x50, "guess password of EM4x50"}, + {"chk", CmdEM4x50Chk, IfPm3EM4x50, "check passwords from dictionary"}, + {"dump", CmdEM4x50Dump, IfPm3EM4x50, "dump EM4x50 tag"}, + {"info", CmdEM4x50Info, IfPm3EM4x50, "tag information EM4x50"}, + {"login", CmdEM4x50Login, IfPm3EM4x50, "login into EM4x50"}, + {"rdbl", CmdEM4x50Read, IfPm3EM4x50, "read word data from EM4x50"}, + {"wrbl", CmdEM4x50Write, IfPm3EM4x50, "write word data to EM4x50"}, + {"writepwd",CmdEM4x50WritePwd, IfPm3EM4x50, "change password of EM4x50"}, + {"wipe", CmdEM4x50Wipe, IfPm3EM4x50, "wipe EM4x50 tag"}, + {"reader", CmdEM4x50Reader, IfPm3EM4x50, "show standard read mode data of EM4x50"}, + {"restore",CmdEM4x50Restore, IfPm3EM4x50, "restore EM4x50 dump to tag"}, + {"sim", CmdEM4x50Sim, IfPm3EM4x50, "simulate EM4x50 tag"}, + {"eload", CmdEM4x50ELoad, IfPm3EM4x50, "upload dump of EM4x50 to flash memory"}, + {"esave", CmdEM4x50ESave, IfPm3EM4x50, "save flash memory to file"}, {NULL, NULL, NULL, NULL} }; 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/cmdlfem4x70.c b/client/src/cmdlfem4x70.c new file mode 100644 index 000000000..ae0b3ad22 --- /dev/null +++ b/client/src/cmdlfem4x70.c @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#include "cmdlfem4x70.h" +#include +#include "cmdparser.h" // command_t +#include "cliparser.h" +#include "fileutils.h" +#include "commonutil.h" +#include "em4x70.h" + + +static int CmdHelp(const char *Cmd); + +static void print_info_result(uint8_t *data) { + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(INFO, "-------------------------------------------------------------"); + + // data section + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, _YELLOW_("EM4x70 data:")); + + for(int i=1; i <= 32; i+=2) { + PrintAndLogEx(NORMAL, "%02X %02X", data[32-i], data[32-i-1]); + } + PrintAndLogEx(NORMAL, "Tag ID: %02X %02X %02X %02X", data[7], data[6], data[5], data[4]); + PrintAndLogEx(NORMAL, "Lockbit 0: %d", (data[3] & 0x40) ? 1:0); + PrintAndLogEx(NORMAL, "Lockbit 1: %d", (data[3] & 0x80) ? 1:0); + PrintAndLogEx(NORMAL, ""); + +} + +int em4x70_info(void) { + + em4x70_data_t edata = { + .parity = false // TODO: try both? or default to true + }; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(edata)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "(em4x70) timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + print_info_result(resp.data.asBytes); + return PM3_SUCCESS; + } + + return PM3_ESOFT; +} + +//quick test for EM4x70 tag +bool detect_4x70_block(void) { + + return em4x70_info() == PM3_SUCCESS; +} + +int CmdEM4x70Info(const char *Cmd) { + + // envoke reading of a EM4x70 tag which has to be on the antenna because + // decoding is done by the device (not on client side) + + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x10 info", + "Tag Information EM4x70\n" + " Tag variants include ID48 automotive transponder.\n" + " ID48 does not use command parity (default).\n" + " V4070 and EM4170 do require parity bit.", + "lf em 4x70 info\n" + "lf em 4x70 -p -> adds parity bit to commands\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0("p", "parity", "Add parity bit when sending commands"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + etd.parity = arg_get_lit(ctx, 0); + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + print_info_result(resp.data.asBytes); + return PM3_SUCCESS; + } + + PrintAndLogEx(FAILED, "reading tag " _RED_("failed")); + return PM3_ESOFT; +} + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"info", CmdEM4x70Info, IfPm3EM4x70, "tag information EM4x70"}, + {NULL, NULL, NULL, NULL} +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdLFEM4X70(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdlfem4x70.h b/client/src/cmdlfem4x70.h new file mode 100644 index 000000000..a529678e7 --- /dev/null +++ b/client/src/cmdlfem4x70.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 commands +//----------------------------------------------------------------------------- + +#ifndef CMDLFEM4X70_H__ +#define CMDLFEM4X70_H__ + +#include "common.h" +#include "em4x50.h" + +#define TIMEOUT 2000 + +int CmdLFEM4X70(const char *Cmd); +int CmdEM4x70Info(const char *Cmd); + +int em4x70_info(void); +bool detect_4x70_block(void); + +#endif diff --git a/client/src/cmdlfnexwatch.c b/client/src/cmdlfnexwatch.c index eefbd03ad..ba6637731 100644 --- a/client/src/cmdlfnexwatch.c +++ b/client/src/cmdlfnexwatch.c @@ -196,12 +196,12 @@ int demodNexWatch(bool verbose) { } // output - PrintAndLogEx(SUCCESS, " NexWatch raw id : " _YELLOW_("0x%"PRIx32), rawid); + PrintAndLogEx(SUCCESS, " NexWatch raw id : " _YELLOW_("0x%08"PRIx32), rawid); if (m_idx < ARRAYLEN(items)) { PrintAndLogEx(SUCCESS, " fingerprint : " _GREEN_("%s"), items[m_idx].desc); } - PrintAndLogEx(SUCCESS, " 88bit id : " _YELLOW_("%"PRIu32) " (" _YELLOW_("0x%"PRIx32)")", cn, cn); + PrintAndLogEx(SUCCESS, " 88bit id : " _YELLOW_("%"PRIu32) " (" _YELLOW_("0x%08"PRIx32)")", cn, cn); PrintAndLogEx(SUCCESS, " mode : %x", mode); if (parity == calc_parity) { @@ -212,7 +212,7 @@ int demodNexWatch(bool verbose) { PrintAndLogEx(DEBUG, " checksum : %s (0x%02X)", (m_idx < ARRAYLEN(items)) ? _GREEN_("ok") : _RED_("fail"), chk); - PrintAndLogEx(INFO, " Raw : " _YELLOW_("%"PRIX32"%"PRIX32"%"PRIX32), raw1, raw2, raw3); + PrintAndLogEx(INFO, " Raw : " _YELLOW_("%08"PRIX32"%08"PRIX32"%08"PRIX32), raw1, raw2, raw3); return PM3_SUCCESS; } diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index 80c8eda4f..a3f5ca20b 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -51,6 +51,7 @@ t55xx_conf_block_t config = { .inverted = false, .offset = 0x00, .block0 = 0x00, + .block0Status = notSet, .Q5 = false, .usepwd = false, .downlink_mode = refFixedBit @@ -739,6 +740,7 @@ static int CmdT55xxSetConfig(const char *Cmd) { for (; i < 9; i++) { if (rates[i] == bitRate) { config.bitrate = i; + config.block0 = ((config.block0 & ~(0x1c0000)) | (i << 18)); break; } } @@ -789,6 +791,7 @@ static int CmdT55xxSetConfig(const char *Cmd) { PrintAndLogEx(WARNING, "Unknown modulation '%s'", modulation); errors = true; } + config.block0 = ((config.block0 & ~(0x1f000)) | (config.modulation << 12)); break; case 'i': if ((param_getchar(Cmd, cmdp + 1) == '0') || (param_getchar(Cmd, cmdp + 1) == '1')) { @@ -822,6 +825,7 @@ static int CmdT55xxSetConfig(const char *Cmd) { config.ST = true; cmdp += 1; } + config.block0 = ((config.block0 & ~(0x8)) | (config.ST << 3)); break; case 'r': errors = param_getdec(Cmd, cmdp + 1, &downlink_mode); @@ -841,10 +845,9 @@ static int CmdT55xxSetConfig(const char *Cmd) { //Validations if (errors) return usage_t55xx_config(); + config.block0Status = userSet; if (gotconf) { SetConfigWithBlock0Ex(block0, config.offset, config.Q5); - } else { - config.block0 = 0; } return printConfiguration(config); @@ -1335,6 +1338,7 @@ bool t55xxTryDetectModulationEx(uint8_t downlink_mode, bool print_config, uint32 config.pwd = pwd & 0xffffffff; } + config.block0Status = autoDetect; if (print_config) printConfiguration(config); @@ -1370,6 +1374,7 @@ bool t55xxTryDetectModulationEx(uint8_t downlink_mode, bool print_config, uint32 PrintAndLogEx(NORMAL, "--[%d]---------------", i + 1); } + config.block0Status = autoDetect; if (print_config) printConfiguration(tests[i]); } @@ -1640,7 +1645,7 @@ int printConfiguration(t55xx_conf_block_t b) { PrintAndLogEx(INFO, " Inverted : %s", (b.inverted) ? _GREEN_("Yes") : "No"); PrintAndLogEx(INFO, " Offset : %d", b.offset); PrintAndLogEx(INFO, " Seq. Term. : %s", (b.ST) ? _GREEN_("Yes") : "No"); - PrintAndLogEx(INFO, " Block0 : 0x%08X", b.block0); + PrintAndLogEx(INFO, " Block0 : 0x%08X %s", b.block0, GetConfigBlock0Source(b.block0Status)); PrintAndLogEx(INFO, " Downlink Mode : %s", GetDownlinkModeStr(b.downlink_mode)); PrintAndLogEx(INFO, " Password Set : %s", (b.usepwd) ? _RED_("Yes") : _GREEN_("No")); if (b.usepwd) { @@ -2800,6 +2805,28 @@ char *GetModelStrFromCID(uint32_t cid) { return buf; } +char *GetConfigBlock0Source(uint8_t id) { + + static char buf[40]; + char *retStr = buf; + + switch (id) { + case autoDetect: + snprintf(retStr, sizeof(buf), _YELLOW_("(Auto detect)")); + break; + case userSet: + snprintf(retStr, sizeof(buf), _YELLOW_("(User set)")); + break; + case tagRead: + snprintf(retStr, sizeof(buf), _GREEN_("(Tag read)")); + break; + default: + snprintf(retStr, sizeof(buf), _RED_("(Unknown)")); + break; + } + return buf; +} + char *GetSelectedModulationStr(uint8_t id) { static char buf[20]; diff --git a/client/src/cmdlft55xx.h b/client/src/cmdlft55xx.h index 4352eed64..90fbaa85d 100644 --- a/client/src/cmdlft55xx.h +++ b/client/src/cmdlft55xx.h @@ -125,6 +125,12 @@ typedef struct { bool inverted; uint8_t offset; uint32_t block0; + enum { + notSet = 0x00, + autoDetect = 0x01, + userSet = 0x02, + tagRead = 0x03, + } block0Status; enum { RF_8 = 0x00, RF_16 = 0x01, @@ -166,6 +172,7 @@ char *GetSaferStr(uint32_t id); char *GetQ5ModulationStr(uint32_t id); char *GetModulationStr(uint32_t id, bool xmode); char *GetModelStrFromCID(uint32_t cid); +char *GetConfigBlock0Source(uint8_t id); char *GetSelectedModulationStr(uint8_t id); char *GetDownlinkModeStr(uint8_t downlink_mode); void printT5xxHeader(uint8_t page); diff --git a/client/src/cmdparser.c b/client/src/cmdparser.c index 2aee4af05..137f72dc7 100644 --- a/client/src/cmdparser.c +++ b/client/src/cmdparser.c @@ -101,6 +101,13 @@ bool IfPm3EM4x50(void) { return pm3_capabilities.compiled_with_em4x50; } +bool IfPm3EM4x70(void) { + + if (!IfPm3Present()) + return false; + return pm3_capabilities.compiled_with_em4x70; +} + bool IfPm3Hfsniff(void) { if (!IfPm3Present()) return false; diff --git a/client/src/cmdparser.h b/client/src/cmdparser.h index 4e1e37a77..ff59df705 100644 --- a/client/src/cmdparser.h +++ b/client/src/cmdparser.h @@ -35,6 +35,7 @@ bool IfPm3FpcUsartFromUsb(void); bool IfPm3Lf(void); bool IfPm3Hitag(void); bool IfPm3EM4x50(void); +bool IfPm3EM4x70(void); bool IfPm3Hfsniff(void); bool IfPm3Hfplot(void); bool IfPm3Iso14443a(void); diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index c38758679..8e154e300 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -123,7 +123,7 @@ int CmdWiegandDecode(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_lit0("p", "parity", "ignore invalid parity"), - arg_strx1(NULL, "raw", "", "raw hex to be decoded"), + arg_strx1("r", "raw", "", "raw hex to be decoded"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); 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/common_arm/Makefile.hal b/common_arm/Makefile.hal index 40e0a2c51..24d31261e 100644 --- a/common_arm/Makefile.hal +++ b/common_arm/Makefile.hal @@ -105,6 +105,9 @@ endif ifneq ($(SKIP_EM4x50),1) PLATFORM_DEFS += -DWITH_EM4x50 endif +ifneq ($(SKIP_EM4x70),1) + PLATFORM_DEFS += -DWITH_EM4x70 +endif # common HF support ifneq ($(SKIP_ISO15693),1) diff --git a/doc/cliparser_todo.txt b/doc/cliparser_todo.txt index 4e1dc76cd..b2571f493 100644 --- a/doc/cliparser_todo.txt +++ b/doc/cliparser_todo.txt @@ -141,13 +141,6 @@ hf mfu otptear hf mfdes enum hf mfdes getuid hf mfdes info -hf thinfilm info -hf thinfilm sim -hf topaz info -hf topaz reader -hf topaz sim -hf topaz sniff -hf topaz raw hw connect hw dbg hw detectreader @@ -174,11 +167,14 @@ lf simfsk lf simpsk lf simbidir lf sniff +lf tune lf em 410x lf em 4x05 lf em 4x50 +lf hitag info lf hitag reader lf hitag sim +lf hitag sniff lf hitag writer lf hitag dump lf hitag cc diff --git a/doc/commands.md b/doc/commands.md index f496ef3fe..44489d546 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -580,6 +580,7 @@ Check column "offline" for their availability. |`lf em 410x `|Y |`EM 410x commands...` |`lf em 4x05 `|Y |`EM 4x05 commands...` |`lf em 4x50 `|Y |`EM 4x50 commands...` +|`lf em 4x70 `|Y |`EM 4x70 commands...` ### lf fdxb diff --git a/include/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/em4x70.h b/include/em4x70.h new file mode 100644 index 000000000..503b5f2e8 --- /dev/null +++ b/include/em4x70.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 sirloins +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Low frequency EM4x70 structs +//----------------------------------------------------------------------------- + +#ifndef EM4X70_H__ +#define EM4X70_H__ + +typedef struct { + bool parity; +} em4x70_data_t; + +#endif /* EM4X70_H__ */ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 6acc1c1c5..217a468e2 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -198,6 +198,7 @@ typedef struct { bool compiled_with_lf : 1; bool compiled_with_hitag : 1; bool compiled_with_em4x50 : 1; + bool compiled_with_em4x70 : 1; // hf bool compiled_with_hfsniff : 1; bool compiled_with_hfplot : 1; @@ -507,9 +508,15 @@ 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 +#define CMD_LF_EM4X70_INFO 0x0260 // Sampling configuration for LF reader/sniffer #define CMD_LF_SAMPLING_SET_CONFIG 0x021D #define CMD_LF_FSK_SIMULATE 0x021E diff --git a/include/protocols.h b/include/protocols.h index fcb446661..dafce0024 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -160,8 +160,8 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MIFARE_MAGICWUPC1 0x40 #define MIFARE_MAGICWUPC2 0x43 #define MIFARE_MAGICWIPEC 0x41 -#define MIFARE_CMD_INC 0xC0 -#define MIFARE_CMD_DEC 0xC1 +#define MIFARE_CMD_DEC 0xC0 +#define MIFARE_CMD_INC 0xC1 #define MIFARE_CMD_RESTORE 0xC2 #define MIFARE_CMD_TRANSFER 0xB0 diff --git a/tools/mf_nonce_brute/protocol.h b/tools/mf_nonce_brute/protocol.h index 50784392f..1eda47986 100644 --- a/tools/mf_nonce_brute/protocol.h +++ b/tools/mf_nonce_brute/protocol.h @@ -6,8 +6,8 @@ #define MIFARE_AUTH_KEYA 0x60 #define MIFARE_AUTH_KEYB 0x61 -#define MIFARE_CMD_INC 0xC0 -#define MIFARE_CMD_DEC 0xC1 +#define MIFARE_CMD_DEC 0xC0 +#define MIFARE_CMD_INC 0xC1 #define MIFARE_CMD_RESTORE 0xC2 #define MIFARE_CMD_TRANSFER 0xB0 diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index ad61dc13d..cdbc5adb4 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -364,7 +364,7 @@ while true; do "AWID - len: 50 FC: 2001 Card: 13371337 - Wiegand: 20fa201980f92, Raw: 0128b12eb1811d7117e22111"; then break; fi if ! CheckExecute slow "lf T55 em410x test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_em410x.pm3; lf search 1'" "EM410x ID found"; then break; fi if ! CheckExecute slow "lf T55 em410x test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_em410x.pm3; lf em 410x demod'" \ - "EM TAG ID : 0F0368568B"; then break; fi + "EM 410x ID 0F0368568B"; then break; fi if ! CheckExecute slow "lf T55 fdxb_animal test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_fdxb_animal.pm3; lf search 1'" "FDX-B ID found"; then break; fi if ! CheckExecute slow "lf T55 fdxb_animal test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_fdxb_animal.pm3; lf fdxb demod'" \ "Animal ID 999-000000112233"; then break; fi @@ -391,7 +391,7 @@ while true; do "Fmt 26 FC: 123 Card: 1337 checksum: 10"; then break; fi if ! CheckExecute slow "lf T55 indala_224 test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_indala_224.pm3; lf search 1'" "Indala ID found"; then break; fi if ! CheckExecute slow "lf T55 indala_224 test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_indala_224.pm3; lf indala demod'" \ - "Indala - len 224 Raw: 80000001b23523a6c2e31eba3cbee4afb3c6ad1fcf649393928c14e5"; then break; fi + "Indala (len 224) Raw: 80000001b23523a6c2e31eba3cbee4afb3c6ad1fcf649393928c14e5"; then break; fi if ! CheckExecute slow "lf T55 io test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_io.pm3; lf search 1'" "IO Prox ID found"; then break; fi if ! CheckExecute slow "lf T55 io test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_io.pm3; lf io demod'" \ "IO Prox - XSF(01)01:01337, Raw: 007840603059cf3f (ok)"; then break; fi @@ -415,7 +415,7 @@ while true; do "NEDAP (64b) - ID: 12345 subtype: 1 customer code: 291 / 0x123 Raw: FF82246508209953"; then break; fi if ! CheckExecute slow "lf T55 nexwatch test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_nexwatch.pm3; lf search 1'" "NexWatch ID found"; then break; fi if ! CheckExecute slow "lf T55 nexwatch test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_nexwatch.pm3; lf nexwatch demod'" \ - "Raw : 56000000213C9F8F150C00"; then break; fi + "Raw : 5600000000213C9F8F150C00"; then break; fi if ! CheckExecute slow "lf T55 nexwatch_nexkey test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_nexwatch_nexkey.pm3; lf search 1'" "NexWatch ID found"; then break; fi if ! CheckExecute slow "lf T55 nexwatch_nexkey test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_nexwatch_nexkey.pm3; lf nexwatch demod'" \ "88bit id : 521512301 (0x1f15a56d)"; then break; fi diff --git a/tools/recover_pk.py b/tools/recover_pk.py index 2431c7a5f..45b0805be 100755 --- a/tools/recover_pk.py +++ b/tools/recover_pk.py @@ -199,6 +199,39 @@ CURVES = { 0xCF5AC8395BAFEB13C02DA292DDED7A83 ) ), + "secp192k1": ( + 711, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37, + 0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D, + 0x000000000000000000000000000000000000000000000000, + 0x000000000000000000000000000000000000000000000003, + ( + 0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D, + 0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D + ) + ), + "secp192r1": ( + 409, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC, + 0x64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1, + ( + 0x188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012, + 0x07192B95FFC8DA78631011ED6B24CDD573F977A11E794811 + ) + ), + "secp224k1": ( + 712, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D, + 0x10000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7, + 0x00000000000000000000000000000000000000000000000000000000, + 0x00000000000000000000000000000000000000000000000000000005, + ( + 0xA1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C, + 0x7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5 + ) + ), "secp224r1": ( 713, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001, @@ -210,6 +243,51 @@ CURVES = { 0xBD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34 ) ), + "secp256k1": ( + 714, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000007, + ( + 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 + ) + ), + ## openssl uses the name: prime256v1. + "secp256r1": ( + 415, + 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF, + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, + 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, + 0x5AC635D8AA3A93E7B3EbBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B, + ( + 0x6B17D1F2E12c4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 + ) + ), + "secp384r1": ( + 715, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC, + 0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF, + ( + 0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7, + 0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F + ) + ), + "secp521r1": ( + 716, + 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409, + 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC, + 0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00, + ( + 0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66, + 0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650 + ) + ) } def get_curve(name): @@ -300,24 +378,28 @@ class EllipticCurve: ####################################################################### -def recover(data, signature, alghash=None): - recovered = set() - if len(signature) == 32: - curve = get_curve("secp128r1") - recoverable = False - elif len(signature) == 33: - curve = get_curve("secp128r1") - recoverable = True - elif len(signature) == 56: - curve = get_curve("secp224r1") - recoverable = False - elif len(signature) == 57: - curve = get_curve("secp224r1") - recoverable = True +def guess_curvename(signature): + l = (len(signature) // 2) & 0xfe + if l == 32 : + curves = [ "secp128r1" ] + elif l == 48: + curves = [ "secp192k1", "secp192r1" ] + elif l == 56: + curves = [ "secp224k1", "secp224r1" ] + elif l == 64: + curves = [ "secp256k1", "secp256r1" ] + elif l == 96: + curves = [ "secp384r1" ] + elif l == 132: + curves = [ "secp521r1" ] else: - print("Unsupported signature size %i" % len(signature)) - exit(1) + raise ValueError("Unsupported signature size %i" % len(signature)) + return curves +def recover(data, signature, curvename, alghash=None): + recovered = set() + curve = get_curve(curvename) + recoverable = len(signature) % 1 == 1 if (recoverable): try: pk = curve.recover(signature, data, hash=alghash) @@ -339,7 +421,7 @@ def recover(data, signature, alghash=None): pass return recovered -def recover_multiple(uids, sigs, alghash=None): +def recover_multiple(uids, sigs, curvename, alghash=None): recovered = set() assert len(uids) == len(sigs) for i in range(len(uids)): @@ -349,7 +431,7 @@ def recover_multiple(uids, sigs, alghash=None): signature = binascii.unhexlify(sigs[i]) if debug: print("Signature (%2i): " % len(signature), binascii.hexlify(signature)) - recovered_tmp = recover(data, signature, alghash) + recovered_tmp = recover(data, signature, curvename, alghash) if i == 0: if recovered_tmp == set(): break @@ -408,12 +490,19 @@ def selftests(): {'name': "ICODE DNA, ICODE SLIX2", 'samples': ["EE5F9B00180104E0", "32D9E7579CD77E6F1FA11419231E874826984C5F189FDE1421684563A9663377"], 'pk': "048878A2A2D3EEC336B4F261A082BD71F9BE11C4E2E896648B32EFA59CEA6E59F0" }, +# uses secp256r1?, SHA-256, +# {'name': "Minecraft Earth", +# 'samples': ["aa", "DF0E506DFF8FCFC4B7B979D917644445F1230D2C7CDC342AFA842CA240C210BE7275F62073A9670F2DCEFC602CBEE771C2B4CD4A04F3D1EA11F49ABDF7E8B721"], +# 'pk': "" }, ] succeeded = True for t in tests: print("Testing %-25s" % (t['name']+":"), end="") - recovered = recover_multiple(t['samples'][::2], t['samples'][1::2]) - recovered |= recover_multiple(t['samples'][::2], t['samples'][1::2], alghash="sha256") + curvenames = guess_curvename(t['samples'][1]) + recovered = set() + for c in curvenames: + for h in [None, "sha1", "sha256", "sha512"]: + recovered |= recover_multiple(t['samples'][::2], t['samples'][1::2], c, alghash=h) if (len(recovered) == 1): pk = recovered.pop() pk = binascii.hexlify(pk).decode('utf8') @@ -442,14 +531,15 @@ if __name__ == "__main__": print("Usage: \n%s UID SIGN [UID SIGN] [...]" % sys.argv[0]) print("Example: \n%s 04ee45daa34084 ebb6102bff74b087d18a57a54bc375159a04ea9bc61080b7f4a85afe1587d73b" % sys.argv[0]) exit(1) - - print("Assuming no hash was used in the signature generation:") - recovered = recover_multiple(sys.argv[1:][::2], sys.argv[1:][1::2]) - print("Possible uncompressed Pk(s):") - for pk in list(recovered): - print(binascii.hexlify(pk).decode('utf8')) - print("Assuming SHA-256 was used in the signature generation:") - recovered = recover_multiple(sys.argv[1:][::2], sys.argv[1:][1::2], alghash="sha256") - print("Possible uncompressed Pk(s):") - for pk in list(recovered): - print(binascii.hexlify(pk).decode('utf8')) + uids, sigs = sys.argv[1:][::2], sys.argv[1:][1::2] + curvenames = guess_curvename(sigs[0]) + for c in curvenames: + print("\nAssuming curve=%s" % c) + print("========================") + for h in [None, "sha1", "sha256", "sha512"]: + print("Assuming hash=%s" % h) + recovered = recover_multiple(uids, sigs, c, alghash=h) + if recovered: + print("Possible uncompressed Pk(s):") + for pk in list(recovered): + print(binascii.hexlify(pk).decode('utf8'))