diff --git a/CHANGELOG.md b/CHANGELOG.md index e168a0db4..a4898db8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added `hf iclass encode` - encode a wiegand binary to a encrypted credential (@iceman1001) - Changed `recoverpk.py` - now tests more ECDSA curves (@doegox) - Added `hf 14a apdufuzz`- a naive apdu cla/ins/p1p2/len fuzzer (@iceman1001) + - Improved `hf 14a apdufuzz/apdufind` to find hidden APDUs (@ikarus23) - Fix mixed up INC/DEC in MIFARE protocol defs (@vortixdev) - Added `lf em 4x70 info` - new support for ID48 transponders (@cmolson) - Fix multiple coverity scan issues (@iceman1001) diff --git a/README.md b/README.md index bc7e88854..b8140e407 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Next place to visit is the [Proxmark Forum](http://www.proxmark.org/forum/index. - [Proxmark3 IRC channel](http://webchat.freenode.net/?channels=#proxmark3) - [Proxmark3 sub reddit](https://www.reddit.com/r/proxmark3/) - [Twitter](https://twitter.com/proxmark3/) - - [Proxmark3 community discord server](https://discord.gg/zjxc8ZB) + - [Proxmark3 community discord server](https://discord.gg/QfPvGFRQxH) _no slack channel_ diff --git a/armsrc/Standalone/lf_tharexde.c b/armsrc/Standalone/lf_tharexde.c index 887100855..f5195513c 100644 --- a/armsrc/Standalone/lf_tharexde.c +++ b/armsrc/Standalone/lf_tharexde.c @@ -88,7 +88,7 @@ static int get_input_data_from_file(uint32_t *words, char *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); @@ -153,7 +153,7 @@ void RunMod(void) { if (button_pressed == BUTTON_SINGLE_CLICK) { SpinUp(100); - + switch (state) { case STATE_SIM: @@ -168,7 +168,7 @@ void RunMod(void) { default: break; } - + state_change = true; } else if (button_pressed == BUTTON_HOLD) { @@ -261,9 +261,9 @@ void RunMod(void) { 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; @@ -277,7 +277,7 @@ void RunMod(void) { 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; @@ -287,7 +287,7 @@ void RunMod(void) { } pwd_found = em4x50_standalone_brute(passwords[0], passwords[1], &pwd); - + if (pwd_found == PM3_ETIMEOUT) { // timeout -> no EM4x50 tag on reader? @@ -313,15 +313,15 @@ void RunMod(void) { 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 diff --git a/armsrc/appmain.c b/armsrc/appmain.c index c4cc6aaf2..fe8566363 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1170,6 +1170,26 @@ static void PacketReceived(PacketCommandNG *packet) { em4x70_info((em4x70_data_t *)packet->data.asBytes); break; } + case CMD_LF_EM4X70_WRITE: { + em4x70_write((em4x70_data_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X70_UNLOCK: { + em4x70_unlock((em4x70_data_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X70_AUTH: { + em4x70_auth((em4x70_data_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X70_WRITEPIN: { + em4x70_write_pin((em4x70_data_t *)packet->data.asBytes); + break; + } + case CMD_LF_EM4X70_WRITEKEY: { + em4x70_write_key((em4x70_data_t *)packet->data.asBytes); + break; + } #endif #ifdef WITH_ISO15693 diff --git a/armsrc/em4x50.c b/armsrc/em4x50.c index a73d8c5dc..bf457c7cf 100644 --- a/armsrc/em4x50.c +++ b/armsrc/em4x50.c @@ -37,11 +37,11 @@ #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 +// 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 @@ -66,18 +66,18 @@ static void wait_timer(uint32_t period) { // 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++) { *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; @@ -107,7 +107,7 @@ static bool extract_parities(uint64_t word, uint32_t *data) { col_parities_calculated ^= (*data >> ((3 - j) * 8 + (7 - i))) & 0x1; } } - + if ((row_parities == row_parities_calculated) && (col_parities == col_parities_calculated)) return true; @@ -192,7 +192,7 @@ static bool get_signalproperties(void) { // about 2 samples per bit period 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; @@ -230,7 +230,7 @@ static bool get_signalproperties(void) { gLow = sample_ref - pct * (sample_max_mean - sample_ref) / 100; LED_A_OFF(); - + return true; } @@ -291,7 +291,7 @@ static uint32_t get_pulse_length(void) { // check if pulse length corresponds to given length static bool check_pulse_length(uint32_t pl, int 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))); } // send single bit according to EM4x50 application note and datasheet @@ -347,12 +347,12 @@ static void em4x50_reader_send_byte_with_parity(uint8_t byte) { // 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_reader_send_byte(bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]); @@ -363,7 +363,7 @@ static void em4x50_reader_send_word(const uint32_t word) { // find single listen window static bool find_single_listen_window(void) { int cnt_pulses = 0; - + LED_B_ON(); while (cnt_pulses < EM4X50_T_WAITING_FOR_SNGLLIW) { @@ -393,7 +393,7 @@ static bool find_single_listen_window(void) { // -> 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) { @@ -453,7 +453,7 @@ static int find_double_listen_window(bool bcommand) { cnt_pulses++; } - LED_B_OFF(); + LED_B_OFF(); return PM3_EFAILED; } @@ -481,7 +481,7 @@ static bool check_ack(bool bliw) { 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. @@ -532,9 +532,9 @@ static int get_word_from_bitstream(uint32_t *data) { 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 @@ -562,7 +562,7 @@ static int get_word_from_bitstream(uint32_t *data) { cnt++; word <<= 1; - + pl = get_pulse_length(); if (check_pulse_length(pl, EM4X50_T_TAG_FULL_PERIOD)) { @@ -613,9 +613,9 @@ static int get_word_from_bitstream(uint32_t *data) { return (extract_parities(word, data)) ? --cnt : 0; } } - + LED_C_OFF(); - + return PM3_EOPABORTED; } @@ -697,7 +697,7 @@ bool em4x50_sim_send_word(uint32_t word) { // 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) { @@ -777,7 +777,7 @@ static bool login(uint32_t password) { // send password em4x50_reader_send_word(password); - + wait_timer(T0 * EM4X50_T_TAG_TPP); // check if ACK is returned @@ -800,7 +800,7 @@ static bool brute(uint32_t start, uint32_t stop, uint32_t *pwd) { for (*pwd = start; *pwd <= stop; (*pwd)++) { if (login(*pwd) == PM3_SUCCESS) { - + pwd_found = true; // to be safe login 5 more times @@ -810,11 +810,11 @@ static bool brute(uint32_t start, uint32_t stop, uint32_t *pwd) { break; } } - + if (pwd_found) break; } - + // print password every 500 iterations if ((++cnt % 500) == 0) { @@ -828,10 +828,10 @@ static bool brute(uint32_t start, uint32_t stop, uint32_t *pwd) { // print data Dbprintf("|%8i | 0x%08x | 0x%08x |", cnt, reflect32(*pwd), *pwd); } - + if (BUTTON_PRESS()) break; - + } // print footer @@ -853,7 +853,7 @@ void em4x50_login(uint32_t *password) { reply_ng(CMD_LF_EM4X50_LOGIN, status, NULL, 0); } -// envoke password search +// envoke password search void em4x50_brute(em4x50_data_t *etd) { em4x50_setup_read(); @@ -904,7 +904,7 @@ void em4x50_chk(uint8_t *filename) { pwd = 0x0; for (int j = 0; j < 4; j++) pwd |= (*(pwds + 4 * i + j)) << ((3 - j) * 8); - + if ((status = login(pwd)) == PM3_SUCCESS) break; } @@ -1062,7 +1062,7 @@ void em4x50_reader(void) { // writes to specified static int write(uint32_t word, uint32_t addresses) { - + if (request_receive_mode() == PM3_SUCCESS) { // send write command @@ -1078,7 +1078,7 @@ static int write(uint32_t word, uint32_t addresses) { 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(T0 * EM4X50_T_TAG_TWA); @@ -1174,7 +1174,7 @@ void em4x50_write(em4x50_data_t *etd) { // if password is given renew login after reset if (etd->pwd_given) status = login(etd->password1); - + if (status == PM3_SUCCESS) { // call a selective read @@ -1224,11 +1224,11 @@ 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(); @@ -1245,7 +1245,7 @@ void em4x50_sim(uint8_t *filename) { 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]) { @@ -1260,7 +1260,7 @@ void em4x50_sim(uint8_t *filename) { // 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++) { @@ -1276,7 +1276,7 @@ void em4x50_sim(uint8_t *filename) { } else { status = PM3_ENODATA; } - + BigBuf_free(); lf_finalize(); reply_ng(CMD_LF_EM4X50_SIM, status, NULL, 0); diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 1feb448c7..1a6f37d56 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -21,28 +21,35 @@ 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 +// Conversion from Ticks to RF periods +// 1 us = 1.5 ticks +// 1RF Period = 8us = 12 Ticks +#define TICKS_PER_FC 12 -#define EM4X70_T_WAITING_FOR_SNGLLIW 160 // Unsure +// Chip timing from datasheet +// Converted into Ticks for timing functions +#define EM4X70_T_TAG_QUARTER_PERIOD (8 * TICKS_PER_FC) +#define EM4X70_T_TAG_HALF_PERIOD (16 * TICKS_PER_FC) +#define EM4X70_T_TAG_THREE_QUARTER_PERIOD (24 * TICKS_PER_FC) +#define EM4X70_T_TAG_FULL_PERIOD (32 * TICKS_PER_FC) // 1 Bit Period +#define EM4X70_T_TAG_TWA (128 * TICKS_PER_FC) // Write Access Time +#define EM4X70_T_TAG_DIV (224 * TICKS_PER_FC) // Divergency Time +#define EM4X70_T_TAG_AUTH (4224 * TICKS_PER_FC) // Authentication Time +#define EM4X70_T_TAG_WEE (3072 * TICKS_PER_FC) // EEPROM write Time +#define EM4X70_T_TAG_TWALB (672 * TICKS_PER_FC) // Write Access Time of Lock Bits +#define EM4X70_T_TAG_BITMOD (4 * TICKS_PER_FC) // Initial time to stop modulation when sending 0 +#define EM4X70_T_TAG_TOLERANCE (8 * TICKS_PER_FC) // Tolerance in RF periods for receive/LIW -#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 +#define EM4X70_T_TAG_TIMEOUT (4 * EM4X70_T_TAG_FULL_PERIOD) // Timeout if we ever get a pulse longer than this +#define EM4X70_T_WAITING_FOR_LIW 50 // Pulses to wait for listen window +#define EM4X70_T_READ_HEADER_LEN 16 // Read header length (16 bit periods) +#define EM4X70_COMMAND_RETRIES 5 // Attempts to send/read command +#define EM4X70_MAX_RECEIVE_LENGTH 96 // Maximum bits to expect from any command /** * These IDs are from the EM4170 datasheet - * Some versions of the chip require a fourth + * Some versions of the chip require a * (even) parity bit, others do not */ #define EM4X70_COMMAND_ID 0x01 @@ -52,24 +59,28 @@ static bool command_parity = true; #define EM4X70_COMMAND_WRITE 0x05 #define EM4X70_COMMAND_UM2 0x07 -static uint8_t gHigh = 0; -static uint8_t gLow = 0; +// Constants used to determing high/low state of signal +#define EM4X70_NOISE_THRESHOLD 13 // May depend on noise in environment +#define HIGH_SIGNAL_THRESHOLD (127 + EM4X70_NOISE_THRESHOLD) +#define LOW_SIGNAL_THRESHOLD (127 - EM4X70_NOISE_THRESHOLD) -#define IS_HIGH(sample) (sample>gLow ? true : false) -#define IS_LOW(sample) (sample LOW_SIGNAL_THRESHOLD ? true : false) +#define IS_LOW(sample) (sample < HIGH_SIGNAL_THRESHOLD ? true : false) + +// Timing related macros #define IS_TIMEOUT(timeout_ticks) (GetTicks() > timeout_ticks) +#define TICKS_ELAPSED(start_ticks) (GetTicks() - start_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 uint8_t bits2byte(const uint8_t *bits, int length); +static void bits2bytes(const uint8_t *bits, int length, uint8_t *out); +static int em4x70_receive(uint8_t *bits, size_t length); static bool find_listen_window(bool command); static void init_tag(void) { - memset(tag.data, 0x00, sizeof(tag.data)/sizeof(tag.data[0])); + memset(tag.data, 0x00, sizeof(tag.data) / sizeof(tag.data[0])); } -static void EM4170_setup_read(void) { +static void em4x70_setup_read(void) { FpgaDownloadAndGo(FPGA_BITSTREAM_LF); FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC | FPGA_LF_ADC_READER_FIELD); @@ -102,208 +113,312 @@ static void EM4170_setup_read(void) { 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)); + // Simple check to ensure we see a signal above the noise threshold + uint32_t no_periods = 32; // wait until signal/noise > 1 (max. 32 periods) - for (int i = 0; i < TICKS_PER_FC * EM4X70_T_TAG_FULL_PERIOD * no_periods; i++) { + for (int i = 0; i < EM4X70_T_TAG_FULL_PERIOD * no_periods; i++) { // about 2 samples per bit period - WaitTicks(TICKS_PER_FC * EM4X70_T_TAG_HALF_PERIOD); + WaitTicks(EM4X70_T_TAG_HALF_PERIOD); - if (AT91C_BASE_SSC->SSC_RHR > noise) { - signal_found = true; - break; + if (AT91C_BASE_SSC->SSC_RHR > HIGH_SIGNAL_THRESHOLD) { + return true; } - } - - 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; + return false; } /** - * get_pulse_length - * - * Times falling edge pulses - */ -static uint32_t get_pulse_length(void) { + * get_falling_pulse_length + * + * Returns time between falling edge pulse in ticks + */ +static uint32_t get_falling_pulse_length(void) { - uint8_t sample; - uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + uint32_t timeout = GetTicks() + EM4X70_T_TAG_TIMEOUT; - do { - sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - }while (IS_HIGH(sample) && !IS_TIMEOUT(timeout)); + while (IS_HIGH(AT91C_BASE_SSC->SSC_RHR) && !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)); + while (IS_LOW(AT91C_BASE_SSC->SSC_RHR) && !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)); + while (IS_HIGH(AT91C_BASE_SSC->SSC_RHR) && !IS_TIMEOUT(timeout)); if (IS_TIMEOUT(timeout)) return 0; - return GetTicks() - start_ticks; + return TICKS_ELAPSED(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) { + * get_rising_pulse_length + * + * Returns time between rising edge pulse in ticks + */ +static uint32_t get_rising_pulse_length(void) { - uint8_t sample; - uint32_t timeout = GetTicks() + (TICKS_PER_FC * 3 * EM4X70_T_TAG_FULL_PERIOD); + uint32_t timeout = GetTicks() + EM4X70_T_TAG_TIMEOUT; - do { - sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - }while (IS_LOW(sample) && !IS_TIMEOUT(timeout)); + while (IS_LOW(AT91C_BASE_SSC->SSC_RHR) && !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)); + while (IS_HIGH(AT91C_BASE_SSC->SSC_RHR) && !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)); + while (IS_LOW(AT91C_BASE_SSC->SSC_RHR) && !IS_TIMEOUT(timeout)); if (IS_TIMEOUT(timeout)) return 0; - return GetTicks() - start_ticks; + return TICKS_ELAPSED(start_ticks); } -static bool check_pulse_length(uint32_t pl, int length, int margin) { +static uint32_t get_pulse_length(edge_detection_t edge) { + + if(edge == RISING_EDGE) + return get_rising_pulse_length(); + else if(edge == FALLING_EDGE) + return get_falling_pulse_length(); + + return 0; +} + +static bool check_pulse_length(uint32_t pl, uint32_t length) { // 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))); + return ((pl >= (length - EM4X70_T_TAG_TOLERANCE)) & (pl <= (length + EM4X70_T_TAG_TOLERANCE))); } -static void em4x70_send_bit(int bit) { +static void em4x70_send_bit(bool 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 + // disable modulation (drop the field) n cycles of carrier LOW(GPIO_SSC_DOUT); - while (GetTicks() - start_ticks <= TICKS_PER_FC * 4); + while (TICKS_ELAPSED(start_ticks) <= EM4X70_T_TAG_BITMOD); // 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); + while (TICKS_ELAPSED(start_ticks) <= 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); + while (TICKS_ELAPSED(start_ticks) <= 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); + while (TICKS_ELAPSED(start_ticks) <= EM4X70_T_TAG_FULL_PERIOD); } - } - /** - * em4x70_send_command + * em4x70_send_nibble + * + * sends 4 bits of data + 1 bit of parity (with_parity) + * */ -static void em4170_send_command(uint8_t command) { +static void em4x70_send_nibble(uint8_t nibble, bool with_parity) { 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) + if (command_parity) msb_bit = 1; - + for (int i = msb_bit; i < 4; i++) { - int bit = (command >> (3 - i)) & 1; + int bit = (nibble >> (3 - i)) & 1; em4x70_send_bit(bit); parity ^= bit; } - if(command_parity) + if (with_parity) em4x70_send_bit(parity); - } -static bool find_listen_window(bool command) { +static void em4x70_send_byte(uint8_t byte) { + // Send byte msb first + for (int i = 0; i < 8; i++) + em4x70_send_bit((byte >> (7 - i)) & 1); +} + +static void em4x70_send_word(const uint16_t word) { + + // Split into nibbles + uint8_t nibbles[4]; + uint8_t j = 0; + for (int i = 0; i < 2; i++) { + uint8_t byte = (word >> (8 * i)) & 0xff; + nibbles[j++] = (byte >> 4) & 0xf; + nibbles[j++] = byte & 0xf; + } + + // send 16 bit word with parity bits according to EM4x70 datasheet + // sent as 4 x nibbles (4 bits + parity) + for (int i = 0; i < 4; i++) { + em4x70_send_nibble(nibbles[i], true); + } + + // send column parities (4 bit) + em4x70_send_nibble(nibbles[0] ^ nibbles[1] ^ nibbles[2] ^ nibbles[3], false); + + // send final stop bit (always "0") + em4x70_send_bit(0); +} + +static bool check_ack(void) { + // returns true if signal structue corresponds to ACK, anything else is + // counted as NAK (-> false) + // ACK 64 + 64 + // NACK 64 + 48 + if (check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) { + // ACK + return true; + } + // Othewise it was a NACK or Listen Window + return false; +} + +static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *response) { + + if (find_listen_window(true)) { + + em4x70_send_nibble(EM4X70_COMMAND_AUTH, true); + + // Send 56-bit Random number + for (int i = 0; i < 7; i++) { + em4x70_send_byte(rnd[i]); + } + + // Send 7 x 0's (Diversity bits) + for (int i = 0; i < 7; i++) { + em4x70_send_bit(0); + } + + // Send 28-bit f(RN) + + // Send first 24 bits + for (int i = 0; i < 3; i++) { + em4x70_send_byte(frnd[i]); + } + + // Send last 4 bits (no parity) + em4x70_send_nibble((frnd[3] >> 4) & 0xf, false); + + // Receive header, 20-bit g(RN), LIW + uint8_t grnd[EM4X70_MAX_RECEIVE_LENGTH] = {0}; + int num = em4x70_receive(grnd, 20); + if (num < 20) { + Dbprintf("Auth failed"); + return PM3_ESOFT; + } + bits2bytes(grnd, 24, response); + return PM3_SUCCESS; + } + + return PM3_ESOFT; +} + +static int send_pin(const uint32_t pin) { + + // sends pin code for unlocking + if (find_listen_window(true)) { + + // send PIN command + em4x70_send_nibble(EM4X70_COMMAND_PIN, true); + + // --> Send TAG ID (bytes 4-7) + for (int i = 0; i < 4; i++) { + em4x70_send_byte(tag.data[7 - i]); + } + + // --> Send PIN + for (int i = 0; i < 4 ; i++) { + em4x70_send_byte((pin >> (i * 8)) & 0xff); + } + + // Wait TWALB (write access lock bits) + WaitTicks(EM4X70_T_TAG_TWALB); + + // <-- Receive ACK + if (check_ack()) { + + // Writes Lock Bits + WaitTicks(EM4X70_T_TAG_WEE); + // <-- Receive header + ID + uint8_t tag_id[EM4X70_MAX_RECEIVE_LENGTH]; + int num = em4x70_receive(tag_id, 32); + if (num < 32) { + Dbprintf("Invalid ID Received"); + return PM3_ESOFT; + } + bits2bytes(tag_id, num, &tag.data[4]); + return PM3_SUCCESS; + } + } + + return PM3_ESOFT; +} + +static int write(const uint16_t word, const uint8_t address) { + + // writes to specified
+ if (find_listen_window(true)) { + + // send write command + em4x70_send_nibble(EM4X70_COMMAND_WRITE, true); + + // send address data with parity bit + em4x70_send_nibble(address, true); + + // send data word + em4x70_send_word(word); + + // Wait TWA + WaitTicks(EM4X70_T_TAG_TWA); + + // look for ACK sequence + if (check_ack()) { + + // now EM4x70 needs EM4X70_T_TAG_TWEE (EEPROM write time) + // for saving data and should return with ACK + WaitTicks(EM4X70_T_TAG_WEE); + if (check_ack()) { + + return PM3_SUCCESS; + } + } + } + return PM3_ESOFT; +} + + +static bool find_listen_window(bool command) { + int cnt = 0; - while(cnt < EM4X70_T_WAITING_FOR_SNGLLIW) { + while (cnt < EM4X70_T_WAITING_FOR_LIW) { /* 80 ( 64 + 16 ) 80 ( 64 + 16 ) @@ -311,26 +426,24 @@ static bool find_listen_window(bool command) { 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; - } - } + if (check_pulse_length(get_pulse_length(RISING_EDGE), (2*EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(RISING_EDGE), (2*EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_HALF_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), (2*EM4X70_T_TAG_FULL_PERIOD) + EM4X70_T_TAG_FULL_PERIOD) && + check_pulse_length(get_pulse_length(FALLING_EDGE), EM4X70_T_TAG_FULL_PERIOD + (2*EM4X70_T_TAG_HALF_PERIOD))) { + + 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( 4 * EM4X70_T_TAG_QUARTER_PERIOD ); + // Send RM Command + em4x70_send_bit(0); + em4x70_send_bit(0); } + return true; } cnt++; } @@ -338,22 +451,21 @@ static bool find_listen_window(bool command) { return false; } -static void bits2bytes(uint8_t *bits, int length, uint8_t *out) { - - if(length%8 != 0) { +static void bits2bytes(const 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]); - } + for (int i = 1; i <= num_bytes; i++) { + out[num_bytes - i] = bits2byte(bits, 8); + bits += 8; + } } -static uint8_t bits2byte(uint8_t *bits, int length) { +static uint8_t bits2byte(const uint8_t *bits, int length) { // converts separate bits into a single "byte" uint8_t byte = 0; @@ -368,161 +480,134 @@ static uint8_t bits2byte(uint8_t *bits, int length) { return byte; } -/*static void print_array(uint8_t *bits, int len) { +static bool send_command_and_read(uint8_t command, uint8_t *bytes, size_t length) { - 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 + int retries = EM4X70_COMMAND_RETRIES; + while (retries) { + retries--; - 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) { +static int em4x70_receive(uint8_t *bits, size_t length) { uint32_t pl; int bit_pos = 0; - uint8_t edge = 0; - - + edge_detection_t edge = RISING_EDGE; 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); + WaitTicks(6 * 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)) { + for(int i = 0; i < EM4X70_T_READ_HEADER_LEN; i++) { + pl = get_pulse_length(edge); + if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD)) { foundheader = true; break; } } - if(!foundheader) { + 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(); + for (int i = 0; i < 3; i++) { + // If pulse length is not 1 bit, then abort early + if(!check_pulse_length(get_pulse_length(edge), EM4X70_T_TAG_FULL_PERIOD)) { + return 0; + } } // identify remaining bits based on pulse lengths - // between two listen windows only pulse lengths of 1, 1.5 and 2 are possible - while (true) { + // between listen windows only pulse lengths of 1, 1.5 and 2 are possible + while (bit_pos < length) { - if(edge) - pl = get_pulse_length(); - else - pl = get_pulse_invert_length(); + pl = get_pulse_length(edge); - if (check_pulse_length(pl, EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + if (check_pulse_length(pl, EM4X70_T_TAG_FULL_PERIOD)) { - // pulse length = 1 - bits[bit_pos++] = edge; + // pulse length 1 -> assign bit + bits[bit_pos++] = edge == FALLING_EDGE ? 1 : 0; - } else if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + } else if (check_pulse_length(pl, 3 * EM4X70_T_TAG_HALF_PERIOD)) { - // pulse length = 1.5 -> flip edge detection - if(edge) { + // pulse length 1.5 -> 2 bits + flip edge detection + if (edge == FALLING_EDGE) { bits[bit_pos++] = 0; bits[bit_pos++] = 0; - edge = 0; + edge = RISING_EDGE; } else { bits[bit_pos++] = 1; bits[bit_pos++] = 1; - edge = 1; + edge = FALLING_EDGE; } - } else if (check_pulse_length(pl, 2 * EM4X70_T_TAG_FULL_PERIOD, EM4X70_T_TAG_QUARTER_PERIOD)) { + } else if (check_pulse_length(pl, 2 * EM4X70_T_TAG_FULL_PERIOD)) { - // pulse length of 2 - if(edge) { + // pulse length of 2 -> two bits + if (edge == FALLING_EDGE) { bits[bit_pos++] = 0; bits[bit_pos++] = 1; } else { @@ -530,29 +615,27 @@ static int em4x70_receive(uint8_t *bits) { 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; + } else { + // Listen Window, or invalid bit + break; } } - 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(); + em4x70_setup_read(); // Find the Tag - if (get_signalproperties() && find_EM4X70_Tag()) { + if (get_signalproperties() && find_em4x70_tag()) { // Read ID, UM1 and UM2 status = em4x70_read_id() && em4x70_read_um1() && em4x70_read_um2(); } @@ -561,3 +644,165 @@ void em4x70_info(em4x70_data_t *etd) { lf_finalize(); reply_ng(CMD_LF_EM4X70_INFO, status, tag.data, sizeof(tag.data)); } + +void em4x70_write(em4x70_data_t *etd) { + + uint8_t status = 0; + + command_parity = etd->parity; + + init_tag(); + em4x70_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_em4x70_tag()) { + + // Write + status = write(etd->word, etd->address) == PM3_SUCCESS; + + if (status) { + // Read Tag after writing + em4x70_read_id(); + em4x70_read_um1(); + em4x70_read_um2(); + } + + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_WRITE, status, tag.data, sizeof(tag.data)); +} + +void em4x70_unlock(em4x70_data_t *etd) { + + uint8_t status = 0; + + command_parity = etd->parity; + + init_tag(); + em4x70_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_em4x70_tag()) { + + // Read ID (required for send_pin command) + if (em4x70_read_id()) { + + // Send PIN + status = send_pin(etd->pin) == PM3_SUCCESS; + + // If the write succeeded, read the rest of the tag + if (status) { + // Read Tag + // ID doesn't change + em4x70_read_um1(); + em4x70_read_um2(); + } + } + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_UNLOCK, status, tag.data, sizeof(tag.data)); +} + +void em4x70_auth(em4x70_data_t *etd) { + + uint8_t status = 0; + uint8_t response[3] = {0}; + + command_parity = etd->parity; + + init_tag(); + em4x70_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_em4x70_tag()) { + + // Authenticate and get tag response + status = authenticate(etd->rnd, etd->frnd, response) == PM3_SUCCESS; + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_AUTH, status, response, sizeof(response)); +} + +void em4x70_write_pin(em4x70_data_t *etd) { + + uint8_t status = 0; + + command_parity = etd->parity; + + init_tag(); + em4x70_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_em4x70_tag()) { + + // Read ID (required for send_pin command) + if (em4x70_read_id()) { + + // Write new PIN + if( (write( etd->pin & 0xFFFF, EM4X70_PIN_WORD_UPPER) == PM3_SUCCESS) && + (write((etd->pin >> 16) & 0xFFFF, EM4X70_PIN_WORD_LOWER) == PM3_SUCCESS)) { + + // Now Try to authenticate using the new PIN + + // Send PIN + status = send_pin(etd->pin) == PM3_SUCCESS; + + // If the write succeeded, read the rest of the tag + if (status) { + // Read Tag + // ID doesn't change + em4x70_read_um1(); + em4x70_read_um2(); + } + } + } + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_WRITEPIN, status, tag.data, sizeof(tag.data)); +} + +void em4x70_write_key(em4x70_data_t *etd) { + + uint8_t status = 0; + + command_parity = etd->parity; + + init_tag(); + em4x70_setup_read(); + + // Find the Tag + if (get_signalproperties() && find_em4x70_tag()) { + + // Read ID to ensure we can write to card + if (em4x70_read_id()) { + status = 1; + + // Write each crypto block + for(int i = 0; i < 6; i++) { + + uint16_t key_word = (etd->crypt_key[(i*2)+1] << 8) + etd->crypt_key[i*2]; + // Write each word, abort if any failure occurs + if (write(key_word, 9-i) != PM3_SUCCESS) { + status = 0; + break; + } + } + // TODO: Ideally here we would perform a test authentication + // to ensure the new key was written correctly. This is + // what the datasheet suggests. We can't do that until + // we have the crypto algorithm implemented. + } + } + + StopTicks(); + lf_finalize(); + reply_ng(CMD_LF_EM4X70_WRITEKEY, status, tag.data, sizeof(tag.data)); +} diff --git a/armsrc/em4x70.h b/armsrc/em4x70.h index 80fd977a9..abebe0e8b 100644 --- a/armsrc/em4x70.h +++ b/armsrc/em4x70.h @@ -17,6 +17,16 @@ typedef struct { uint8_t data[32]; } em4x70_tag_t; +typedef enum { + RISING_EDGE, + FALLING_EDGE +}edge_detection_t; + void em4x70_info(em4x70_data_t *etd); +void em4x70_write(em4x70_data_t *etd); +void em4x70_unlock(em4x70_data_t *etd); +void em4x70_auth(em4x70_data_t *etd); +void em4x70_write_pin(em4x70_data_t *etd); +void em4x70_write_key(em4x70_data_t *etd); #endif /* EM4x70_H */ diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index b6d1b7ed8..ef4695e20 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1727,7 +1727,7 @@ void SimTagIso15693(uint8_t *uid) { if ((cmd_len >= 5) && (cmd[0] & ISO15_REQ_INVENTORY) && (cmd[1] == ISO15_CMD_INVENTORY)) { bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - + // Build INVENTORY command uint8_t resp_inv[CMD_INV_RESP] = {0}; @@ -1743,30 +1743,30 @@ void SimTagIso15693(uint8_t *uid) { resp_inv[7] = uid[2]; resp_inv[8] = uid[1]; resp_inv[9] = uid[0]; - + // CRC AddCrc15(resp_inv, 10); CodeIso15693AsTag(resp_inv, CMD_INV_RESP); - + tosend_t *ts = get_tosend(); - + TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); LogTrace_ISO15693(resp_inv, CMD_INV_RESP, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); chip_state = SELECTED; } - + // GET_SYSTEM_INFO if ((cmd[1] == ISO15_CMD_SYSINFO)) { bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - + // Build GET_SYSTEM_INFO command uint8_t resp_sysinfo[CMD_SYSINFO_RESP] = {0}; - + resp_sysinfo[0] = 0; // Response flags. resp_sysinfo[1] = 0x0F; // Information flags (0x0F - DSFID, AFI, Mem size, IC) - + // 64-bit UID resp_sysinfo[2] = uid[7]; resp_sysinfo[3] = uid[6]; @@ -1776,42 +1776,42 @@ void SimTagIso15693(uint8_t *uid) { resp_sysinfo[7] = uid[2]; resp_sysinfo[8] = uid[1]; resp_sysinfo[9] = uid[0]; - + resp_sysinfo[10] = 0; // DSFID resp_sysinfo[11] = 0; // AFI resp_sysinfo[12] = 0x1B; // Memory size. resp_sysinfo[13] = 0x03; // Memory size. resp_sysinfo[14] = 0x01; // IC reference. - + // CRC AddCrc15(resp_sysinfo, 15); CodeIso15693AsTag(resp_sysinfo, CMD_SYSINFO_RESP); - + tosend_t *ts = get_tosend(); - + TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); LogTrace_ISO15693(resp_sysinfo, CMD_SYSINFO_RESP, response_time * 32, (response_time * 32) + (ts->max * 32 * 64), NULL, false); } - + // READ_BLOCK if ((cmd[1] == ISO15_CMD_READ)) { bool slow = !(cmd[0] & ISO15_REQ_DATARATE_HIGH); uint32_t response_time = reader_eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; - + // Build GET_SYSTEM_INFO command uint8_t resp_readblock[CMD_READBLOCK_RESP] = {0}; - + resp_readblock[0] = 0; // Response flags. resp_readblock[1] = 0; // Block data. resp_readblock[2] = 0; // Block data. resp_readblock[3] = 0; // Block data. resp_readblock[4] = 0; // Block data. - + // CRC AddCrc15(resp_readblock, 5); CodeIso15693AsTag(resp_readblock, CMD_READBLOCK_RESP); - + tosend_t *ts = get_tosend(); TransmitTo15693Reader(ts->buf, ts->max, &response_time, 0, slow); diff --git a/armsrc/lfsampling.c b/armsrc/lfsampling.c index 7a42367dc..ef1d128cc 100644 --- a/armsrc/lfsampling.c +++ b/armsrc/lfsampling.c @@ -515,7 +515,7 @@ void doCotagAcquisition(void) { if (BUTTON_PRESS()) break; - + if (checker == 4000) { if (data_available()) break; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 4ea7acf36..c67104158 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -228,6 +228,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhf15.c ${PM3_ROOT}/client/src/cmdhfcryptorf.c ${PM3_ROOT}/client/src/cmdhfepa.c + ${PM3_ROOT}/client/src/cmdhfemrtd.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c ${PM3_ROOT}/client/src/cmdhficlass.c diff --git a/client/Makefile b/client/Makefile index 9c624722c..9cfc4fc4b 100644 --- a/client/Makefile +++ b/client/Makefile @@ -469,6 +469,7 @@ SRCS = aiddesfire.c \ cmdhf15.c \ cmdhfcryptorf.c \ cmdhfepa.c \ + cmdhfemrtd.c \ cmdhffelica.c \ cmdhffido.c \ cmdhficlass.c \ diff --git a/client/dictionaries/mfdes_default_keys.dic b/client/dictionaries/mfdes_default_keys.dic index 4c863c3f5..a26d13d32 100644 --- a/client/dictionaries/mfdes_default_keys.dic +++ b/client/dictionaries/mfdes_default_keys.dic @@ -8,6 +8,7 @@ 43464F494D48504E4C4359454E528841 #NHIF 6AC292FAA1315B4D858AB3A3D7D5933A 404142434445464748494a4b4c4d4e4f +3112B738D8862CCD34302EB299AAB456 # Gallagher AES (https://pastebin.com/GkbGLz8r) 00112233445566778899aabbccddeeff 2b7e151628aed2a6abf7158809cf4f3c fbeed618357133667c85e08f7236a8de @@ -43,4 +44,4 @@ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 404142434445464748494a4b4c4d4e4f 303132333435363738393a3b3c3d3e3f 9CABF398358405AE2F0E2B3D31C99A8A # Default key -605F5E5D5C5B5A59605F5E5D5C5B5A59 # access control \ No newline at end of file +605F5E5D5C5B5A59605F5E5D5C5B5A59 # access control diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index fa100e13d..94985579e 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -23,6 +23,7 @@ #include "cmdhf14b.h" // ISO14443-B #include "cmdhf15.h" // ISO15693 #include "cmdhfepa.h" +#include "cmdhfemrtd.h" // eMRTD #include "cmdhflegic.h" // LEGIC #include "cmdhficlass.h" // ICLASS #include "cmdhfmf.h" // CLASSIC @@ -357,24 +358,25 @@ int CmdHFPlot(const char *Cmd) { static command_t CommandTable[] = { {"--------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("High Frequency") " -----------------------"}, - {"14a", CmdHF14A, AlwaysAvailable, "{ ISO14443A RFIDs... }"}, - {"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"}, - {"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"}, -// {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"}, - {"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"}, - {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, - {"fido", CmdHFFido, AlwaysAvailable, "{ FIDO and FIDO2 authenticators... }"}, - {"iclass", CmdHFiClass, AlwaysAvailable, "{ ICLASS RFIDs... }"}, - {"legic", CmdHFLegic, AlwaysAvailable, "{ LEGIC RFIDs... }"}, - {"lto", CmdHFLTO, AlwaysAvailable, "{ LTO Cartridge Memory RFIDs... }"}, - {"mf", CmdHFMF, AlwaysAvailable, "{ MIFARE RFIDs... }"}, - {"mfp", CmdHFMFP, AlwaysAvailable, "{ MIFARE Plus RFIDs... }"}, - {"mfu", CmdHFMFUltra, AlwaysAvailable, "{ MIFARE Ultralight RFIDs... }"}, - {"mfdes", CmdHFMFDes, AlwaysAvailable, "{ MIFARE Desfire RFIDs... }"}, - {"st", CmdHF_ST, AlwaysAvailable, "{ ST Rothult RFIDs... }"}, - {"thinfilm", CmdHFThinfilm, AlwaysAvailable, "{ Thinfilm RFIDs... }"}, - {"topaz", CmdHFTopaz, AlwaysAvailable, "{ TOPAZ (NFC Type 1) RFIDs... }"}, - {"waveshare", CmdHFWaveshare, AlwaysAvailable, "{ Waveshare NFC ePaper... }"}, + {"14a", CmdHF14A, AlwaysAvailable, "{ ISO14443A RFIDs... }"}, + {"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"}, + {"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"}, +// {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"}, + {"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"}, + {"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"}, + {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, + {"fido", CmdHFFido, AlwaysAvailable, "{ FIDO and FIDO2 authenticators... }"}, + {"iclass", CmdHFiClass, AlwaysAvailable, "{ ICLASS RFIDs... }"}, + {"legic", CmdHFLegic, AlwaysAvailable, "{ LEGIC RFIDs... }"}, + {"lto", CmdHFLTO, AlwaysAvailable, "{ LTO Cartridge Memory RFIDs... }"}, + {"mf", CmdHFMF, AlwaysAvailable, "{ MIFARE RFIDs... }"}, + {"mfp", CmdHFMFP, AlwaysAvailable, "{ MIFARE Plus RFIDs... }"}, + {"mfu", CmdHFMFUltra, AlwaysAvailable, "{ MIFARE Ultralight RFIDs... }"}, + {"mfdes", CmdHFMFDes, AlwaysAvailable, "{ MIFARE Desfire RFIDs... }"}, + {"st", CmdHF_ST, AlwaysAvailable, "{ ST Rothult RFIDs... }"}, + {"thinfilm", CmdHFThinfilm, AlwaysAvailable, "{ Thinfilm RFIDs... }"}, + {"topaz", CmdHFTopaz, AlwaysAvailable, "{ TOPAZ (NFC Type 1) RFIDs... }"}, + {"waveshare", CmdHFWaveshare, AlwaysAvailable, "{ Waveshare NFC ePaper... }"}, {"-----------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"}, {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdTraceList, AlwaysAvailable, "List protocol data in trace buffer"}, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 576979aad..d9ff4c3b7 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -2127,128 +2127,163 @@ static uint16_t get_sw(uint8_t *d, uint8_t n) { n -= 2; return d[n] * 0x0100 + d[n + 1]; } -static int CmdHf14AFuzzapdu(const char *Cmd) { + +static int CmdHf14AFindapdu(const char *Cmd) { + // TODO: What response values should be considerd "valid" or "instersting" (worth dispalying)? + // TODO: Option to select AID/File (and skip INS 0xA4). + // TODO: Validate the decoding of the APDU (not specific to this command, check + // https://cardwerk.com/smartcards/smartcard_standard_ISO7816-4_5_basic_organizations.aspx#chap5_3_2). + // TODO: Check all cases (APDUs) with no data bytes (no/short/extended length). + // TODO: Option to blacklist instructions (or whole APDUs). CLIParserContext *ctx; - CLIParserInit(&ctx, "hf 14a apdufuzz", - "Fuzz APDU's of ISO7816 protocol to find valid CLS/INS/P1P2/LE commands.\n" + CLIParserInit(&ctx, "hf 14a apdufind", + "Enumerate APDU's of ISO7816 protocol to find valid CLS/INS/P1P2 commands.\n" "It loops all 256 possible values for each byte.\n" + "The loop oder is INS -> P1/P2 (alternating) -> CLA\n" "Tag must be on antenna before running.", - "hf 14a apdufuzz\n" - "hf 14a apdufuzz --cla 80\n" - ); + "hf 14a apdufind\n" + "hf 14a apdufind --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_str0("c", "cla", "", "Start value of CLASS (1 hex byte)"), + arg_str0("i", "ins", "", "Start value of INSTRUCTION (1 hex byte)"), + arg_str0(NULL, "p1", "", "Start value of P1 (1 hex byte)"), + arg_str0(NULL, "p2", "", "Start value of P2 (1 hex byte)"), + arg_u64_0("r", "reset", "", "Minimum secondes before resetting the tag (to prevent timeout issues). Default is 5 minutes"), + arg_lit0("v", "verbose", "Verbose output"), arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, false); + CLIExecWithReturn(ctx, Cmd, argtable, true); int cla_len = 0; - uint8_t cla[1] = {0}; - CLIGetHexWithReturn(ctx, 1, cla, &cla_len); - + uint8_t cla_arg[1] = {0}; + CLIGetHexWithReturn(ctx, 1, cla_arg, &cla_len); int ins_len = 0; - uint8_t ins[1] = {0}; - CLIGetHexWithReturn(ctx, 2, ins, &ins_len); - + uint8_t ins_arg[1] = {0}; + CLIGetHexWithReturn(ctx, 2, ins_arg, &ins_len); int p1_len = 0; - uint8_t p1[1] = {0}; - CLIGetHexWithReturn(ctx, 3, p1, &p1_len); - + uint8_t p1_arg[1] = {0}; + CLIGetHexWithReturn(ctx, 3, p1_arg, &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); - + uint8_t p2_arg[1] = {0}; + CLIGetHexWithReturn(ctx, 4, p2_arg, &p2_len); + uint64_t reset_time = arg_get_u64_def(ctx, 5, 5 * 60); // Reset every 5 minutes. 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 cla = cla_arg[0]; + uint8_t ins = ins_arg[0]; + uint8_t p1 = p1_arg[0]; + uint8_t p2 = p2_arg[0]; uint8_t response[PM3_CMD_DATA_SIZE]; - int resplen = 0; - + int response_n = 0; uint8_t aSELECT_AID[80]; int aSELECT_AID_n = 0; + + // Check if the tag reponds to APDUs. + PrintAndLogEx(INFO, "Sending a test APDU (select file command) to check if the tag is responding to APDU"); 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); + int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, true, false, response, sizeof(response), &response_n); if (res) { - DropField(); + PrintAndLogEx(FAILED, "Tag did not responde to a test APDU (select file command). Aborting"); return res; } + PrintAndLogEx(SUCCESS, "Got response. Starting the APDU finder [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2); + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); - if (activate_field) - activate_field = false; + bool inc_p1 = true; + uint64_t t_start = msclock(); + uint64_t t_last_reset = msclock(); - uint64_t t1 = msclock(); + // Enumerate APDUs. do { do { do { - do { - do { - if (kbd_enter_pressed()) { - goto out; - } + // Exit (was the Enter key pressed)? + if (kbd_enter_pressed()) { + PrintAndLogEx(INFO, "User interrupted detected. Aborting"); + goto out; + } - uint8_t foo[5] = {a, b, c, d, e}; - int foo_n = sizeof(foo); + if (verbose) { + PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2); + } - 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; - } + // Send APDU. + uint8_t command[4] = {cla, ins, p1, p2}; + int command_n = sizeof(command); + res = ExchangeAPDU14a(command, command_n, activate_field, keep_field_on, response, sizeof(response), &response_n); + if (res) { + 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); - } + // Was there and length error? If so, try with Le length (case 2 instad of case 1, + // https://stackoverflow.com/a/30679558). Le = 0x00 will get interpreted as extended length APDU + // with Le being 0x0100. + uint16_t sw = get_sw(response, response_n); + bool command_with_le = false; + if (sw == 0x6700) { + PrintAndLogEx(INFO, "Got response for APDU \"%02X%02X%02X%02X\": %04X (%s)", cla, ins, p1, p2, + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + PrintAndLogEx(INFO, "Resending current command with Le = 0x0100 (extended length APDU)"); + uint8_t command2[7] = {cla, ins, p1, p2, 0x00}; + int command2_n = sizeof(command2); + res = ExchangeAPDU14a(command2, command2_n, activate_field, keep_field_on, response, sizeof(response), &response_n); + if (res) { + continue; + } + command_with_le = true; + } - } 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); + // Check response. + sw = get_sw(response, response_n); + if (sw != 0x6a86 && + sw != 0x6986 && + sw != 0x6d00 + ) { + if (command_with_le) { + PrintAndLogEx(INFO, "Got response for APDU \"%02X%02X%02X%02X00\": %04X (%s)", cla, ins, p1, p2, + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } else { + PrintAndLogEx(INFO, "Got response for APDU \"%02X%02X%02X%02X\": %04X (%s)", cla, ins, p1, p2, + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } + // Show response data. + if (response_n > 2) { + PrintAndLogEx(SUCCESS, "Response data is: %s | %s", sprint_hex_inrow(response, response_n - 2), + sprint_ascii(response, response_n - 2)); + } + } + activate_field = false; // Do not reativate the filed until the next reset. + } while (++ins != ins_arg[0]); + // Increment P1/P2 in an alternating fashion. + if (inc_p1) { + p1++; + } else { + p2++; + } + inc_p1 = !inc_p1; + // Check if re-selecting the card is needed. + uint64_t t_since_last_reset = ((msclock() - t_last_reset) / 1000); + if (t_since_last_reset > reset_time) { + DropField(); + activate_field = true; + t_last_reset = msclock(); + PrintAndLogEx(INFO, "Last reset was %" PRIu64 " seconds ago. Reseting the tag to prevent timeout issues", t_since_last_reset); + } + PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2); + } while (p1 != p1_arg[0] || p2 != p2_arg[0]); + cla++; + PrintAndLogEx(INFO, "Status: [ CLA " _GREEN_("%02X") " INS " _GREEN_("%02X") " P1 " _GREEN_("%02X") " P2 " _GREEN_("%02X") " ]", cla, ins, p1, p2); + } while (cla != cla_arg[0]); out: - PrintAndLogEx(SUCCESS, "time: %" PRIu64 " seconds\n", (msclock() - t1) / 1000); + PrintAndLogEx(SUCCESS, "Runtime: %" PRIu64 " seconds\n", (msclock() - t_start) / 1000); DropField(); return PM3_SUCCESS; } @@ -2266,7 +2301,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"}, + {"apdufind", CmdHf14AFindapdu, IfPm3Iso14443a, "Enuerate APDUs - CLA/INS/P1P2"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 3f4890581..e21509fe8 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -26,6 +26,7 @@ #include "mifare/ndef.h" // NDEFRecordsDecodeAndPrint #define TIMEOUT 2000 +#define APDU_TIMEOUT 4000 // iso14b apdu input frame length static uint16_t apdu_frame_length = 0; @@ -1438,7 +1439,7 @@ static int handle_14b_apdu(bool chainingin, uint8_t *datain, int datainlen, bool SendCommandMIX(CMD_HF_ISO14443B_COMMAND, ISO14B_APDU | flags, 0, 0, NULL, 0); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT)) { + if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, APDU_TIMEOUT)) { uint8_t *recv = resp.data.asBytes; int rlen = resp.oldarg[0]; uint8_t res = resp.oldarg[1]; @@ -1488,7 +1489,7 @@ static int handle_14b_apdu(bool chainingin, uint8_t *datain, int datainlen, bool return PM3_SUCCESS; } -static int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, bool leave_signal_on, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { +int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, bool leave_signal_on, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { *dataoutlen = 0; bool chaining = false; int res; diff --git a/client/src/cmdhf14b.h b/client/src/cmdhf14b.h index 2058ea8ba..d236bb6cf 100644 --- a/client/src/cmdhf14b.h +++ b/client/src/cmdhf14b.h @@ -15,6 +15,8 @@ int CmdHF14B(const char *Cmd); +int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, bool leave_signal_on, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); + int infoHF14B(bool verbose); int readHF14B(bool verbose); #endif diff --git a/client/src/cmdhfemrtd.c b/client/src/cmdhfemrtd.c new file mode 100644 index 000000000..daa51be19 --- /dev/null +++ b/client/src/cmdhfemrtd.c @@ -0,0 +1,1330 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 A. Ozkal +// +// 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. +//----------------------------------------------------------------------------- +// High frequency Electronic Machine Readable Travel Document commands +//----------------------------------------------------------------------------- + +// This code is heavily based on mrpkey.py of RFIDIOt + +#include "cmdhfemrtd.h" +#include +#include "fileutils.h" // saveFile +#include "cmdparser.h" // command_t +#include "cmdtrace.h" // CmdTraceList +#include "cliparser.h" // CLIParserContext etc +#include "cmdhf14a.h" // ExchangeAPDU14a +#include "protocols.h" // definitions of ISO14A/7816 protocol +#include "emv/apduinfo.h" // GetAPDUCodeDescription +#include "sha1.h" // KSeed calculation etc +#include "mifare/desfire_crypto.h" // des_encrypt/des_decrypt +#include "des.h" // mbedtls_des_key_set_parity +#include "cmdhf14b.h" // exchange_14b_apdu +#include "iso14b.h" // ISO14B_CONNECT etc +#include "crapto1/crapto1.h" // prng_successor +#include "commonutil.h" // num_to_bytes +#include "util_posix.h" // msclock + +// Max file size in bytes. Used in several places. +// Average EF_DG2 seems to be around 20-25kB or so, but ICAO doesn't set an upper limit +// Iris data seems to be suggested to be around 35kB per eye (Presumably bumping up the file size to around 70kB) +// but as we cannot read that until we implement PACE, 35k seems to be a safe point. +#define EMRTD_MAX_FILE_SIZE 35000 + +// ISO7816 commands +#define EMRTD_SELECT "A4" +#define EMRTD_EXTERNAL_AUTHENTICATE "82" +#define EMRTD_GET_CHALLENGE "84" +#define EMRTD_READ_BINARY "B0" +#define EMRTD_P1_SELECT_BY_EF "02" +#define EMRTD_P1_SELECT_BY_NAME "04" +#define EMRTD_P2_PROPRIETARY "0C" + +// File IDs +#define EMRTD_EF_CARDACCESS "011C" +#define EMRTD_EF_COM "011E" +#define EMRTD_EF_DG1 "0101" +#define EMRTD_EF_DG2 "0102" +#define EMRTD_EF_DG3 "0103" +#define EMRTD_EF_DG4 "0104" +#define EMRTD_EF_DG5 "0105" +#define EMRTD_EF_DG6 "0106" +#define EMRTD_EF_DG7 "0107" +#define EMRTD_EF_DG8 "0108" +#define EMRTD_EF_DG9 "0109" +#define EMRTD_EF_DG10 "010A" +#define EMRTD_EF_DG11 "010B" +#define EMRTD_EF_DG12 "010C" +#define EMRTD_EF_DG13 "010D" +#define EMRTD_EF_DG14 "010E" +#define EMRTD_EF_DG15 "010F" +#define EMRTD_EF_DG16 "0110" +#define EMRTD_EF_SOD "011D" + +// App IDs +#define EMRTD_AID_MRTD "A0000002471001" + +// DESKey Types +const uint8_t KENC_type[4] = {0x00, 0x00, 0x00, 0x01}; +const uint8_t KMAC_type[4] = {0x00, 0x00, 0x00, 0x02}; + +static int CmdHelp(const char *Cmd); + +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 bool emrtd_exchange_commands(const char *cmd, uint8_t *dataout, int *dataoutlen, bool activate_field, bool keep_field_on, bool use_14b) { + uint8_t response[PM3_CMD_DATA_SIZE]; + int resplen = 0; + + PrintAndLogEx(DEBUG, "Sending: %s", cmd); + + uint8_t aCMD[PM3_CMD_DATA_SIZE]; + int aCMD_n = 0; + param_gethex_to_eol(cmd, 0, aCMD, sizeof(aCMD), &aCMD_n); + int res; + if (use_14b) { + res = exchange_14b_apdu(aCMD, aCMD_n, activate_field, keep_field_on, response, sizeof(response), &resplen); + } else { + res = ExchangeAPDU14a(aCMD, aCMD_n, activate_field, keep_field_on, response, sizeof(response), &resplen); + } + if (res) { + DropField(); + return false; + } + + if (resplen < 2) { + return false; + } + PrintAndLogEx(DEBUG, "Response: %s", sprint_hex(response, resplen)); + + // drop sw + memcpy(dataout, &response, resplen - 2); + *dataoutlen = (resplen - 2); + + uint16_t sw = get_sw(response, resplen); + if (sw != 0x9000) { + PrintAndLogEx(DEBUG, "Command %s failed (%04x - %s).", cmd, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return false; + } + return true; +} + +static int emrtd_exchange_commands_noout(const char *cmd, bool activate_field, bool keep_field_on, bool use_14b) { + uint8_t response[PM3_CMD_DATA_SIZE]; + int resplen = 0; + + return emrtd_exchange_commands(cmd, response, &resplen, activate_field, keep_field_on, use_14b); +} + +static char emrtd_calculate_check_digit(char *data) { + int mrz_weight[] = {7, 3, 1}; + int cd = 0; + int value = 0; + char d; + + for (int i = 0; i < strlen(data); i++) { + d = data[i]; + if ('A' <= d && d <= 'Z') { + value = d - 55; + } else if ('a' <= d && d <= 'z') { + value = d - 87; + } else if (d == '<') { + value = 0; + } else { // Numbers + value = d - 48; + } + cd += value * mrz_weight[i % 3]; + } + return cd % 10; +} + +static int emrtd_get_asn1_data_length(uint8_t *datain, int datainlen, int offset) { + PrintAndLogEx(DEBUG, "asn1datalength, datain: %s", sprint_hex_inrow(datain, datainlen)); + int lenfield = (int) * (datain + offset); + PrintAndLogEx(DEBUG, "asn1datalength, lenfield: %i", lenfield); + if (lenfield <= 0x7f) { + return lenfield; + } else if (lenfield == 0x81) { + return ((int) * (datain + offset + 1)); + } else if (lenfield == 0x82) { + return ((int) * (datain + offset + 1) << 8) | ((int) * (datain + offset + 2)); + } else if (lenfield == 0x83) { + return (((int) * (datain + offset + 1) << 16) | ((int) * (datain + offset + 2)) << 8) | ((int) * (datain + offset + 3)); + } + return false; +} + +static int emrtd_get_asn1_field_length(uint8_t *datain, int datainlen, int offset) { + PrintAndLogEx(DEBUG, "asn1fieldlength, datain: %s", sprint_hex_inrow(datain, datainlen)); + int lenfield = (int) * (datain + offset); + PrintAndLogEx(DEBUG, "asn1fieldlength, thing: %i", lenfield); + if (lenfield <= 0x7f) { + return 1; + } else if (lenfield == 0x81) { + return 2; + } else if (lenfield == 0x82) { + return 3; + } else if (lenfield == 0x83) { + return 4; + } + return false; +} + +static void des_encrypt_ecb(uint8_t *key, uint8_t *input, uint8_t *output) { + mbedtls_des_context ctx_enc; + mbedtls_des_setkey_enc(&ctx_enc, key); + mbedtls_des_crypt_ecb(&ctx_enc, input, output); + mbedtls_des_free(&ctx_enc); +} + +static void des_decrypt_ecb(uint8_t *key, uint8_t *input, uint8_t *output) { + mbedtls_des_context ctx_dec; + mbedtls_des_setkey_dec(&ctx_dec, key); + mbedtls_des_crypt_ecb(&ctx_dec, input, output); + mbedtls_des_free(&ctx_dec); +} + +static void des3_encrypt_cbc(uint8_t *iv, uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { + mbedtls_des3_context ctx; + mbedtls_des3_set2key_enc(&ctx, key); + + mbedtls_des3_crypt_cbc(&ctx // des3_context + , MBEDTLS_DES_ENCRYPT // int mode + , inputlen // length + , iv // iv[8] + , input // input + , output // output + ); + mbedtls_des3_free(&ctx); +} + +static void des3_decrypt_cbc(uint8_t *iv, uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { + mbedtls_des3_context ctx; + mbedtls_des3_set2key_dec(&ctx, key); + + mbedtls_des3_crypt_cbc(&ctx // des3_context + , MBEDTLS_DES_DECRYPT // int mode + , inputlen // length + , iv // iv[8] + , input // input + , output // output + ); + mbedtls_des3_free(&ctx); +} + +static int pad_block(uint8_t *input, int inputlen, uint8_t *output) { + uint8_t padding[8] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + memcpy(output, input, inputlen); + + int to_pad = (8 - (inputlen % 8)); + + for (int i = 0; i < to_pad; i++) { + output[inputlen + i] = padding[i]; + } + + return inputlen + to_pad; +} + +static void retail_mac(uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { + // This code assumes blocklength (n) = 8, and input len of up to 240 or so chars + // This code takes inspirations from https://github.com/devinvenable/iso9797algorithm3 + uint8_t k0[8]; + uint8_t k1[8]; + uint8_t intermediate[8] = {0x00}; + uint8_t intermediate_des[256]; + uint8_t block[8]; + uint8_t message[256]; + + // Populate keys + memcpy(k0, key, 8); + memcpy(k1, key + 8, 8); + + // Prepare message + int blocksize = pad_block(input, inputlen, message); + + // Do chaining and encryption + for (int i = 0; i < (blocksize / 8); i++) { + memcpy(block, message + (i * 8), 8); + + // XOR + for (int x = 0; x < 8; x++) { + intermediate[x] = intermediate[x] ^ block[x]; + } + + des_encrypt_ecb(k0, intermediate, intermediate_des); + memcpy(intermediate, intermediate_des, 8); + } + + + des_decrypt_ecb(k1, intermediate, intermediate_des); + memcpy(intermediate, intermediate_des, 8); + + des_encrypt_ecb(k0, intermediate, intermediate_des); + memcpy(output, intermediate_des, 8); +} + +static void emrtd_deskey(uint8_t *seed, const uint8_t *type, int length, uint8_t *dataout) { + PrintAndLogEx(DEBUG, "seed.............. %s", sprint_hex_inrow(seed, 16)); + + // combine seed and type + uint8_t data[50]; + memcpy(data, seed, length); + memcpy(data + length, type, 4); + PrintAndLogEx(DEBUG, "data.............. %s", sprint_hex_inrow(data, length + 4)); + + // SHA1 the key + unsigned char key[64]; + mbedtls_sha1(data, length + 4, key); + PrintAndLogEx(DEBUG, "key............... %s", sprint_hex_inrow(key, length + 4)); + + // Set parity bits + for (int i = 0; i < ((length + 4) / 8); i++) { + mbedtls_des_key_set_parity(key + (i * 8)); + } + PrintAndLogEx(DEBUG, "post-parity key... %s", sprint_hex_inrow(key, 20)); + + memcpy(dataout, &key, length); +} + +static int emrtd_select_file(const char *select_by, const char *file_id, bool use_14b) { + int file_id_len = strlen(file_id) / 2; + + char cmd[50]; + sprintf(cmd, "00%s%s0C%02X%s", EMRTD_SELECT, select_by, file_id_len, file_id); + + return emrtd_exchange_commands_noout(cmd, false, true, use_14b); +} + +static int emrtd_get_challenge(int length, uint8_t *dataout, int *dataoutlen, bool use_14b) { + char cmd[50]; + sprintf(cmd, "00%s0000%02X", EMRTD_GET_CHALLENGE, length); + + return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b); +} + +static int emrtd_external_authenticate(uint8_t *data, int length, uint8_t *dataout, int *dataoutlen, bool use_14b) { + char cmd[100]; + sprintf(cmd, "00%s0000%02X%s%02X", EMRTD_EXTERNAL_AUTHENTICATE, length, sprint_hex_inrow(data, length), length); + return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b); +} + +static int _emrtd_read_binary(int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) { + char cmd[50]; + sprintf(cmd, "00%s%04i%02i", EMRTD_READ_BINARY, offset, bytes_to_read); + + return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b); +} + +static void emrtd_bump_ssc(uint8_t *ssc) { + PrintAndLogEx(DEBUG, "ssc-b: %s", sprint_hex_inrow(ssc, 8)); + for (int i = 7; i > 0; i--) { + if ((*(ssc + i)) == 0xFF) { + // Set anything already FF to 0, we'll do + 1 on num to left anyways + (*(ssc + i)) = 0; + continue; + } + (*(ssc + i)) += 1; + PrintAndLogEx(DEBUG, "ssc-a: %s", sprint_hex_inrow(ssc, 8)); + return; + } +} + +static bool emrtd_check_cc(uint8_t *ssc, uint8_t *key, uint8_t *rapdu, int rapdulength) { + // https://elixi.re/i/clarkson.png + uint8_t k[500]; + uint8_t cc[500]; + + emrtd_bump_ssc(ssc); + + memcpy(k, ssc, 8); + int length = 0; + int length2 = 0; + + if (*(rapdu) == 0x87) { + length += 2 + (*(rapdu + 1)); + memcpy(k + 8, rapdu, length); + PrintAndLogEx(DEBUG, "len1: %i", length); + } + + if ((*(rapdu + length)) == 0x99) { + length2 += 2 + (*(rapdu + (length + 1))); + memcpy(k + length + 8, rapdu + length, length2); + PrintAndLogEx(DEBUG, "len2: %i", length2); + } + + int klength = length + length2 + 8; + + retail_mac(key, k, klength, cc); + PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); + PrintAndLogEx(DEBUG, "rapdu: %s", sprint_hex_inrow(rapdu, rapdulength)); + PrintAndLogEx(DEBUG, "rapdu cut: %s", sprint_hex_inrow(rapdu + (rapdulength - 8), 8)); + PrintAndLogEx(DEBUG, "k: %s", sprint_hex_inrow(k, klength)); + + return memcmp(cc, rapdu + (rapdulength - 8), 8) == 0; +} + +static void _emrtd_convert_filename(const char *file, uint8_t *dataout) { + char temp[3] = {0x00}; + memcpy(temp, file, 2); + dataout[0] = (int)strtol(temp, NULL, 16); + memcpy(temp, file + 2, 2); + dataout[1] = (int)strtol(temp, NULL, 16); +} + +static bool emrtd_secure_select_file(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, const char *select_by, const char *file, bool use_14b) { + uint8_t response[PM3_CMD_DATA_SIZE]; + int resplen = 0; + + // convert filename of string to bytes + uint8_t file_id[2]; + _emrtd_convert_filename(file, file_id); + + uint8_t iv[8] = { 0x00 }; + char command[PM3_CMD_DATA_SIZE]; + uint8_t cmd[8]; + uint8_t data[21]; + uint8_t temp[8] = {0x0c, 0xa4, strtol(select_by, NULL, 16), 0x0c}; + + int cmdlen = pad_block(temp, 4, cmd); + int datalen = pad_block(file_id, 2, data); + PrintAndLogEx(DEBUG, "cmd: %s", sprint_hex_inrow(cmd, cmdlen)); + PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, datalen)); + + des3_encrypt_cbc(iv, kenc, data, datalen, temp); + PrintAndLogEx(DEBUG, "temp: %s", sprint_hex_inrow(temp, datalen)); + uint8_t do87[11] = {0x87, 0x09, 0x01}; + memcpy(do87 + 3, temp, datalen); + PrintAndLogEx(DEBUG, "do87: %s", sprint_hex_inrow(do87, datalen + 3)); + + uint8_t m[19]; + memcpy(m, cmd, cmdlen); + memcpy(m + cmdlen, do87, (datalen + 3)); + PrintAndLogEx(DEBUG, "m: %s", sprint_hex_inrow(m, datalen + cmdlen + 3)); + + emrtd_bump_ssc(ssc); + + uint8_t n[27]; + memcpy(n, ssc, 8); + memcpy(n + 8, m, (cmdlen + datalen + 3)); + PrintAndLogEx(DEBUG, "n: %s", sprint_hex_inrow(n, (cmdlen + datalen + 11))); + + uint8_t cc[8]; + retail_mac(kmac, n, (cmdlen + datalen + 11), cc); + PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); + + uint8_t do8e[10] = {0x8E, 0x08}; + memcpy(do8e + 2, cc, 8); + PrintAndLogEx(DEBUG, "do8e: %s", sprint_hex_inrow(do8e, 10)); + + int lc = datalen + 3 + 10; + PrintAndLogEx(DEBUG, "lc: %i", lc); + + memcpy(data, do87, datalen + 3); + memcpy(data + (datalen + 3), do8e, 10); + PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); + + sprintf(command, "0C%s%s0C%02X%s00", EMRTD_SELECT, select_by, lc, sprint_hex_inrow(data, lc)); + PrintAndLogEx(DEBUG, "command: %s", command); + + if (emrtd_exchange_commands(command, response, &resplen, false, true, use_14b) == false) { + return false; + } + + return emrtd_check_cc(ssc, kmac, response, resplen); +} + +static bool _emrtd_secure_read_binary(uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) { + char command[54]; + uint8_t cmd[8]; + uint8_t data[21]; + uint8_t temp[8] = {0x0c, 0xb0}; + + PrintAndLogEx(DEBUG, "kmac: %s", sprint_hex_inrow(kmac, 20)); + + // Set p1 and p2 + temp[2] = (uint8_t)(offset >> 8); + temp[3] = (uint8_t)(offset >> 0); + + int cmdlen = pad_block(temp, 4, cmd); + PrintAndLogEx(DEBUG, "cmd: %s", sprint_hex_inrow(cmd, cmdlen)); + + uint8_t do97[3] = {0x97, 0x01, bytes_to_read}; + + uint8_t m[11]; + memcpy(m, cmd, 8); + memcpy(m + 8, do97, 3); + + emrtd_bump_ssc(ssc); + + uint8_t n[19]; + memcpy(n, ssc, 8); + memcpy(n + 8, m, 11); + PrintAndLogEx(DEBUG, "n: %s", sprint_hex_inrow(n, 19)); + + uint8_t cc[8]; + retail_mac(kmac, n, 19, cc); + PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); + + uint8_t do8e[10] = {0x8E, 0x08}; + memcpy(do8e + 2, cc, 8); + PrintAndLogEx(DEBUG, "do8e: %s", sprint_hex_inrow(do8e, 10)); + + int lc = 13; + PrintAndLogEx(DEBUG, "lc: %i", lc); + + memcpy(data, do97, 3); + memcpy(data + 3, do8e, 10); + PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); + + sprintf(command, "0C%s%04X%02X%s00", EMRTD_READ_BINARY, offset, lc, sprint_hex_inrow(data, lc)); + PrintAndLogEx(DEBUG, "command: %s", command); + + if (emrtd_exchange_commands(command, dataout, dataoutlen, false, true, use_14b) == false) { + return false; + } + + return emrtd_check_cc(ssc, kmac, dataout, *dataoutlen); +} + +static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) { + uint8_t response[500]; + uint8_t temp[500]; + int resplen, cutat = 0; + uint8_t iv[8] = { 0x00 }; + + if (_emrtd_secure_read_binary(kmac, ssc, offset, bytes_to_read, response, &resplen, use_14b) == false) { + return false; + } + + PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: encrypted: %s", offset, bytes_to_read, sprint_hex_inrow(response, resplen)); + + cutat = ((int) response[1]) - 1; + + des3_decrypt_cbc(iv, kenc, response + 3, cutat, temp); + memcpy(dataout, temp, bytes_to_read); + PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: decrypted: %s", offset, bytes_to_read, sprint_hex_inrow(temp, cutat)); + PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: decrypted and cut: %s", offset, bytes_to_read, sprint_hex_inrow(dataout, bytes_to_read)); + *dataoutlen = bytes_to_read; + return true; +} + + +static int emrtd_read_file(uint8_t *dataout, int *dataoutlen, uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, bool use_secure, bool use_14b) { + uint8_t response[EMRTD_MAX_FILE_SIZE]; + int resplen = 0; + uint8_t tempresponse[500]; + int tempresplen = 0; + int toread = 4; + int offset = 0; + + if (use_secure == true) { + if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, response, &resplen, use_14b) == false) { + return false; + } + } else { + if (_emrtd_read_binary(offset, toread, response, &resplen, use_14b) == false) { + return false; + } + } + + int datalen = emrtd_get_asn1_data_length(response, resplen, 1); + int readlen = datalen - (3 - emrtd_get_asn1_field_length(response, resplen, 1)); + offset = 4; + + while (readlen > 0) { + toread = readlen; + if (readlen > 118) { + toread = 118; + } + + if (kenc == NULL) { + if (_emrtd_read_binary(offset, toread, tempresponse, &tempresplen, use_14b) == false) { + return false; + } + } else { + if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, tempresponse, &tempresplen, use_14b) == false) { + return false; + } + } + + memcpy(response + resplen, tempresponse, tempresplen); + offset += toread; + readlen -= toread; + resplen += tempresplen; + } + + memcpy(dataout, &response, resplen); + *dataoutlen = resplen; + return true; +} + +static bool emrtd_lds_get_data_by_tag(uint8_t *datain, int *datainlen, uint8_t *dataout, int *dataoutlen, int tag1, int tag2, bool twobytetag) { + int offset = 2; + int elementidlen = 0; + int elementlen = 0; + while (offset < *datainlen) { + PrintAndLogEx(DEBUG, "emrtd_lds_get_data_by_tag, offset: %i, data: %X", offset, *(datain + offset)); + // Determine element ID length to set as offset on asn1datalength + if ((*(datain + offset) == 0x5f) || (*(datain + offset) == 0x7f)) { + elementidlen = 2; + } else { + elementidlen = 1; + } + + // Get the length of the element + elementlen = emrtd_get_asn1_data_length(datain + offset, *datainlen - offset, elementidlen); + + // If the element is what we're looking for, get the data and return true + if (*(datain + offset) == tag1 && (!twobytetag || *(datain + offset + 1) == tag2)) { + *dataoutlen = elementlen; + memcpy(dataout, datain + offset + elementidlen + 1, elementlen); + return true; + } + offset += elementidlen + elementlen + 1; + } + // Return false if we can't find the relevant element + return false; +} + +static bool emrtd_file_tag_to_file_id(uint8_t *datain, char *filenameout, char *dataout) { + // imagine bothering with a hashmap or writing good code + // couldn't be me + switch (*datain) { + case 0x60: + memcpy(dataout, EMRTD_EF_COM, 4); + memcpy(filenameout, "EF_COM", 6); + break; + case 0x61: + memcpy(dataout, EMRTD_EF_DG1, 4); + memcpy(filenameout, "EF_DG1", 6); + break; + case 0x75: + memcpy(dataout, EMRTD_EF_DG2, 4); + memcpy(filenameout, "EF_DG2", 6); + break; + // These cases are commented out as they require PACE + // case 0x63: + // memcpy(dataout, EMRTD_EF_DG3, 4); + // memcpy(filenameout, "EF_DG3", 6); + // break; + // case 0x76: + // memcpy(dataout, EMRTD_EF_DG4, 4); + // memcpy(filenameout, "EF_DG4", 6); + // break; + case 0x65: + memcpy(dataout, EMRTD_EF_DG5, 4); + memcpy(filenameout, "EF_DG5", 6); + break; + case 0x66: + memcpy(dataout, EMRTD_EF_DG6, 4); + memcpy(filenameout, "EF_DG6", 6); + break; + case 0x67: + memcpy(dataout, EMRTD_EF_DG7, 4); + memcpy(filenameout, "EF_DG7", 6); + break; + case 0x68: + memcpy(dataout, EMRTD_EF_DG8, 4); + memcpy(filenameout, "EF_DG8", 6); + break; + case 0x69: + memcpy(dataout, EMRTD_EF_DG9, 4); + memcpy(filenameout, "EF_DG9", 6); + break; + case 0x6a: + memcpy(dataout, EMRTD_EF_DG10, 4); + memcpy(filenameout, "EF_DG10", 7); + break; + case 0x6b: + memcpy(dataout, EMRTD_EF_DG11, 4); + memcpy(filenameout, "EF_DG11", 7); + break; + case 0x6c: + memcpy(dataout, EMRTD_EF_DG12, 4); + memcpy(filenameout, "EF_DG12", 7); + break; + case 0x6d: + memcpy(dataout, EMRTD_EF_DG13, 4); + memcpy(filenameout, "EF_DG13", 7); + break; + case 0x6e: + memcpy(dataout, EMRTD_EF_DG14, 4); + memcpy(filenameout, "EF_DG14", 7); + break; + case 0x6f: + memcpy(dataout, EMRTD_EF_DG15, 4); + memcpy(filenameout, "EF_DG15", 7); + break; + case 0x70: + memcpy(dataout, EMRTD_EF_DG16, 4); + memcpy(filenameout, "EF_DG16", 7); + break; + case 0x77: + memcpy(dataout, EMRTD_EF_SOD, 4); + memcpy(filenameout, "EF_SOD", 6); + break; + default: + return false; + } + return true; +} + +static bool emrtd_select_and_read(uint8_t *dataout, int *dataoutlen, const char *file, uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, bool use_secure, bool use_14b) { + if (use_secure == true) { + if (emrtd_secure_select_file(ks_enc, ks_mac, ssc, EMRTD_P1_SELECT_BY_EF, file, use_14b) == false) { + PrintAndLogEx(ERR, "Failed to secure select %s.", file); + return false; + } + } else { + if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, file, use_14b) == false) { + PrintAndLogEx(ERR, "Failed to select %s.", file); + return false; + } + } + + if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure, use_14b) == false) { + PrintAndLogEx(ERR, "Failed to read %s.", file); + return false; + } + return true; +} + +static bool emrtd_dump_file(uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, const char *file, const char *name, bool use_secure, bool use_14b) { + uint8_t response[EMRTD_MAX_FILE_SIZE]; + int resplen = 0; + + if (emrtd_select_and_read(response, &resplen, file, ks_enc, ks_mac, ssc, use_secure, use_14b) == false) { + return false; + } + + PrintAndLogEx(INFO, "Read %s, len: %i.", name, resplen); + PrintAndLogEx(DEBUG, "Contents (may be incomplete over 2k chars): %s", sprint_hex_inrow(response, resplen)); + saveFile(name, ".BIN", response, resplen); + + return true; +} + +static void rng(int length, uint8_t *dataout) { + // Do very very secure prng operations + //for (int i = 0; i < (length / 4); i++) { + // num_to_bytes(prng_successor(msclock() + i, 32), 4, &dataout[i * 4]); + //} + memset(dataout, 0x00, length); +} + +static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac, bool use_14b) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + int resplen = 0; + + uint8_t rnd_ic[8] = { 0x00 }; + uint8_t kenc[50] = { 0x00 }; + uint8_t kmac[50] = { 0x00 }; + uint8_t k_icc[16] = { 0x00 }; + uint8_t S[32] = { 0x00 }; + + uint8_t rnd_ifd[8], k_ifd[16]; + rng(8, rnd_ifd); + rng(16, k_ifd); + + PrintAndLogEx(DEBUG, "doc............... " _GREEN_("%s"), documentnumber); + PrintAndLogEx(DEBUG, "dob............... " _GREEN_("%s"), dob); + PrintAndLogEx(DEBUG, "exp............... " _GREEN_("%s"), expiry); + + char documentnumbercd = emrtd_calculate_check_digit(documentnumber); + char dobcd = emrtd_calculate_check_digit(dob); + char expirycd = emrtd_calculate_check_digit(expiry); + + char kmrz[25]; + sprintf(kmrz, "%s%i%s%i%s%i", documentnumber, documentnumbercd, dob, dobcd, expiry, expirycd); + PrintAndLogEx(DEBUG, "kmrz.............. " _GREEN_("%s"), kmrz); + + uint8_t kseed[16] = { 0x00 }; + mbedtls_sha1((unsigned char *)kmrz, strlen(kmrz), kseed); + PrintAndLogEx(DEBUG, "kseed (sha1)...... %s ", sprint_hex_inrow(kseed, 16)); + + emrtd_deskey(kseed, KENC_type, 16, kenc); + emrtd_deskey(kseed, KMAC_type, 16, kmac); + PrintAndLogEx(DEBUG, "kenc.............. %s", sprint_hex_inrow(kenc, 16)); + PrintAndLogEx(DEBUG, "kmac.............. %s", sprint_hex_inrow(kmac, 16)); + + // Get Challenge + if (emrtd_get_challenge(8, rnd_ic, &resplen, use_14b) == false) { + PrintAndLogEx(ERR, "Couldn't get challenge."); + return false; + } + PrintAndLogEx(DEBUG, "rnd_ic............ %s", sprint_hex_inrow(rnd_ic, 8)); + + memcpy(S, rnd_ifd, 8); + memcpy(S + 8, rnd_ic, 8); + memcpy(S + 16, k_ifd, 16); + + PrintAndLogEx(DEBUG, "S................. %s", sprint_hex_inrow(S, 32)); + + uint8_t iv[8] = { 0x00 }; + uint8_t e_ifd[32] = { 0x00 }; + + des3_encrypt_cbc(iv, kenc, S, sizeof(S), e_ifd); + PrintAndLogEx(DEBUG, "e_ifd............. %s", sprint_hex_inrow(e_ifd, 32)); + + uint8_t m_ifd[8] = { 0x00 }; + + retail_mac(kmac, e_ifd, 32, m_ifd); + PrintAndLogEx(DEBUG, "m_ifd............. %s", sprint_hex_inrow(m_ifd, 8)); + + uint8_t cmd_data[40]; + memcpy(cmd_data, e_ifd, 32); + memcpy(cmd_data + 32, m_ifd, 8); + + // Do external authentication + if (emrtd_external_authenticate(cmd_data, sizeof(cmd_data), response, &resplen, use_14b) == false) { + PrintAndLogEx(ERR, "Couldn't do external authentication. Did you supply the correct MRZ info?"); + return false; + } + PrintAndLogEx(INFO, "External authentication with BAC successful."); + + uint8_t dec_output[32] = { 0x00 }; + des3_decrypt_cbc(iv, kenc, response, 32, dec_output); + PrintAndLogEx(DEBUG, "dec_output........ %s", sprint_hex_inrow(dec_output, 32)); + + if (memcmp(rnd_ifd, dec_output + 8, 8) != 0) { + PrintAndLogEx(ERR, "Challenge failed, rnd_ifd does not match."); + return false; + } + + memcpy(k_icc, dec_output + 16, 16); + + // Calculate session keys + for (int x = 0; x < 16; x++) { + kseed[x] = k_ifd[x] ^ k_icc[x]; + } + + PrintAndLogEx(DEBUG, "kseed............ %s", sprint_hex_inrow(kseed, 16)); + + emrtd_deskey(kseed, KENC_type, 16, ks_enc); + emrtd_deskey(kseed, KMAC_type, 16, ks_mac); + + PrintAndLogEx(DEBUG, "ks_enc........ %s", sprint_hex_inrow(ks_enc, 16)); + PrintAndLogEx(DEBUG, "ks_mac........ %s", sprint_hex_inrow(ks_mac, 16)); + + memcpy(ssc, rnd_ic + 4, 4); + memcpy(ssc + 4, rnd_ifd + 4, 4); + + PrintAndLogEx(DEBUG, "ssc........... %s", sprint_hex_inrow(ssc, 8)); + + return true; +} + +static bool emrtd_connect(bool *use_14b) { + // Try to 14a + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); + PacketResponseNG resp; + bool failed_14a = false; + if (!WaitForResponseTimeout(CMD_ACK, &resp, 2500)) { + DropField(); + failed_14a = true; + } + + if (failed_14a || resp.oldarg[0] == 0) { + PrintAndLogEx(INFO, "No eMRTD spotted with 14a, trying 14b."); + // If not 14a, try to 14b + SendCommandMIX(CMD_HF_ISO14443B_COMMAND, ISO14B_CONNECT | ISO14B_SELECT_STD, 0, 0, NULL, 0); + if (!WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, 2500)) { + PrintAndLogEx(INFO, "No eMRTD spotted with 14b, exiting."); + return false; + } + + if (resp.oldarg[0] != 0) { + PrintAndLogEx(INFO, "No eMRTD spotted with 14b, exiting."); + return false; + } + *use_14b = true; + } + return true; +} + +static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool *BAC, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac, bool *use_14b) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + int resplen = 0; + + // Select and read EF_CardAccess + if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, EMRTD_EF_CARDACCESS, *use_14b)) { + emrtd_read_file(response, &resplen, NULL, NULL, NULL, false, *use_14b); + PrintAndLogEx(INFO, "Read EF_CardAccess, len: %i.", resplen); + PrintAndLogEx(DEBUG, "Contents (may be incomplete over 2k chars): %s", sprint_hex_inrow(response, resplen)); + } else { + PrintAndLogEx(INFO, "PACE unsupported. Will not read EF_CardAccess."); + } + + // Select MRTD applet + if (emrtd_select_file(EMRTD_P1_SELECT_BY_NAME, EMRTD_AID_MRTD, *use_14b) == false) { + PrintAndLogEx(ERR, "Couldn't select the MRTD application."); + return false; + } + + // Select EF_COM + if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, EMRTD_EF_COM, *use_14b) == false) { + *BAC = true; + PrintAndLogEx(INFO, "Basic Access Control is enforced. Will attempt external authentication."); + } else { + *BAC = false; + // Select EF_DG1 + emrtd_select_file(EMRTD_P1_SELECT_BY_EF, EMRTD_EF_DG1, *use_14b); + + if (emrtd_read_file(response, &resplen, NULL, NULL, NULL, false, *use_14b) == false) { + *BAC = true; + PrintAndLogEx(INFO, "Basic Access Control is enforced. Will attempt external authentication."); + } else { + *BAC = false; + PrintAndLogEx(INFO, "EF_DG1: %s", sprint_hex(response, resplen)); + } + } + + // Do Basic Access Aontrol + if (*BAC) { + // If BAC isn't available, exit out and warn user. + if (!BAC_available) { + PrintAndLogEx(ERR, "This eMRTD enforces Basic Access Control, but you didn't supply MRZ data. Cannot proceed."); + PrintAndLogEx(HINT, "Check out hf emrtd dump --help, supply data with -n -d and -e."); + return false; + } + + if (emrtd_do_bac(documentnumber, dob, expiry, ssc, ks_enc, ks_mac, *use_14b) == false) { + return false; + } + } + + return true; +} + +int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + int resplen = 0; + uint8_t ssc[8] = { 0x00 }; + uint8_t ks_enc[16] = { 0x00 }; + uint8_t ks_mac[16] = { 0x00 }; + bool BAC = false; + bool use_14b = false; + + // Select the eMRTD + if (!emrtd_connect(&use_14b)) { + DropField(); + return PM3_ESOFT; + } + + // Authenticate with the eMRTD + if (!emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac, &use_14b)) { + DropField(); + return PM3_ESOFT; + } + + // Select EF_COM + if (!emrtd_select_and_read(response, &resplen, EMRTD_EF_COM, ks_enc, ks_mac, ssc, BAC, use_14b)) { + PrintAndLogEx(ERR, "Failed to read EF_COM."); + DropField(); + return PM3_ESOFT; + } + PrintAndLogEx(INFO, "Read EF_COM, len: %i.", resplen); + PrintAndLogEx(DEBUG, "Contents (may be incomplete over 2k chars): %s", sprint_hex_inrow(response, resplen)); + saveFile("EF_COM", ".BIN", response, resplen); + + uint8_t filelist[50]; + int filelistlen = 0; + + if (!emrtd_lds_get_data_by_tag(response, &resplen, filelist, &filelistlen, 0x5c, 0x00, false)) { + PrintAndLogEx(ERR, "Failed to read file list from EF_COM."); + DropField(); + return PM3_ESOFT; + } + + PrintAndLogEx(DEBUG, "File List: %s", sprint_hex_inrow(filelist, filelistlen)); + + // Dump all files in the file list + for (int i = 0; i < filelistlen; i++) { + char file_id[5] = { 0x00 }; + char file_name[8] = { 0x00 }; + if (emrtd_file_tag_to_file_id(&filelist[i], file_name, file_id) == false) { + PrintAndLogEx(INFO, "File tag not found, skipping: %02X", filelist[i]); + continue; + } + PrintAndLogEx(DEBUG, "Current file: %s", file_name); + emrtd_dump_file(ks_enc, ks_mac, ssc, file_id, file_name, BAC, use_14b); + } + + // Dump EF_SOD + emrtd_dump_file(ks_enc, ks_mac, ssc, EMRTD_EF_SOD, "EF_SOD", BAC, use_14b); + + DropField(); + return PM3_SUCCESS; +} + +static bool emrtd_compare_check_digit(char *datain, int datalen, char expected_check_digit) { + char tempdata[90] = { 0x00 }; + memcpy(tempdata, datain, datalen); + + uint8_t check_digit = emrtd_calculate_check_digit(tempdata) + 0x30; + bool res =check_digit == expected_check_digit; + PrintAndLogEx(DEBUG, "emrtd_compare_check_digit, expected %c == %c calculated ( %s )" + , expected_check_digit + , check_digit + , (res) ? _GREEN_("ok") : _RED_("fail")); + return res; +} + +static bool emrtd_mrz_verify_check_digit(char *mrz, int offset, int datalen) { + char tempdata[90] = { 0x00 }; + memcpy(tempdata, mrz + offset, datalen); + return emrtd_compare_check_digit(tempdata, datalen, mrz[offset + datalen]); +} + +static void emrtd_print_legal_sex(char *legal_sex) { + char sex[12] = { 0x00 }; + switch (*legal_sex) { + case 'M': + strncpy(sex, "Male", 5); + break; + case 'F': + strncpy(sex, "Female", 7); + break; + case '<': + strncpy(sex, "Unspecified", 12); + break; + } + PrintAndLogEx(SUCCESS, "Legal Sex Marker......: " _YELLOW_("%s"), sex); +} + +static int emrtd_mrz_determine_length(char *mrz, int offset, int max_length) { + int i; + for (i = max_length; i >= 0; i--) { + if (mrz[offset + i - 1] != '<') { + break; + } + } + return i; +} + +static int emrtd_mrz_determine_separator(char *mrz, int offset, int max_length) { + int i; + for (i = max_length; i >= 0; i--) { + if (mrz[offset + i - 1] == '<' && mrz[offset + i] == '<') { + break; + } + } + return i - 1; +} + +static void emrtd_print_optional_elements(char *mrz, int offset, int length, bool verify_check_digit) { + int i = emrtd_mrz_determine_length(mrz, offset, length); + + // Only print optional elements if they're available + if (i != 0) { + PrintAndLogEx(SUCCESS, "Optional elements.....: " _YELLOW_("%.*s"), i, mrz + offset); + } + + if (verify_check_digit && !emrtd_mrz_verify_check_digit(mrz, offset, length)) { + PrintAndLogEx(SUCCESS, _RED_("Optional element check digit is invalid.")); + } +} + +static void emrtd_print_document_number(char *mrz, int offset) { + int i = emrtd_mrz_determine_length(mrz, offset, 9); + + PrintAndLogEx(SUCCESS, "Document Number.......: " _YELLOW_("%.*s"), i, mrz + offset); + + if (!emrtd_mrz_verify_check_digit(mrz, offset, 9)) { + PrintAndLogEx(SUCCESS, _RED_("Document number check digit is invalid.")); + } +} + +static void emrtd_print_name(char *mrz, int offset, int max_length) { + char final_name[100] = { 0x00 }; + int i = emrtd_mrz_determine_length(mrz, offset, max_length); + int sep = emrtd_mrz_determine_separator(mrz, offset, i); + int namelen = (i - (sep + 2)); + + memcpy(final_name, mrz + offset + sep + 2, namelen); + final_name[namelen] = ' '; + memcpy(final_name + namelen + 1, mrz + offset, sep); + + PrintAndLogEx(SUCCESS, "Legal Name............: " _YELLOW_("%s"), final_name); +} + +static void emrtd_mrz_convert_date(char *mrz, int offset, char *final_date, bool is_expiry) { + char temp_year[3] = { 0x00 }; + + memcpy(temp_year, mrz + offset, 2); + // If it's > 20, assume 19xx. + if (strtol(temp_year, NULL, 10) < 20 || is_expiry) { + final_date[0] = '2'; + final_date[1] = '0'; + } else { + final_date[0] = '1'; + final_date[1] = '9'; + } + + memcpy(final_date + 2, mrz + offset, 2); + final_date[4] = '-'; + memcpy(final_date + 5, mrz + offset + 2, 2); + final_date[7] = '-'; + memcpy(final_date + 8, mrz + offset + 4, 2); +} + +static void emrtd_print_dob(char *mrz, int offset) { + char final_date[12] = { 0x00 }; + emrtd_mrz_convert_date(mrz, offset, final_date, false); + + PrintAndLogEx(SUCCESS, "Date of birth.........: " _YELLOW_("%s"), final_date); + + if (!emrtd_mrz_verify_check_digit(mrz, offset, 6)) { + PrintAndLogEx(SUCCESS, _RED_("Date of Birth check digit is invalid.")); + } +} + +static void emrtd_print_expiry(char *mrz, int offset) { + char final_date[12] = { 0x00 }; + emrtd_mrz_convert_date(mrz, offset, final_date, true); + + PrintAndLogEx(SUCCESS, "Date of expiry........: " _YELLOW_("%s"), final_date); + + if (!emrtd_mrz_verify_check_digit(mrz, offset, 6)) { + PrintAndLogEx(SUCCESS, _RED_("Date of expiry check digit is invalid.")); + } +} + +int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + int resplen = 0; + uint8_t ssc[8] = { 0x00 }; + uint8_t ks_enc[16] = { 0x00 }; + uint8_t ks_mac[16] = { 0x00 }; + bool BAC = false; + bool use_14b = false; + + int td_variant = 0; + + // Select the eMRTD + if (!emrtd_connect(&use_14b)) { + DropField(); + return PM3_ESOFT; + } + + // Select and authenticate with the eMRTD + bool auth_result = emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac, &use_14b); + PrintAndLogEx(SUCCESS, "Communication standard: %s", use_14b ? _YELLOW_("ISO/IEC 14443(B)") : _YELLOW_("ISO/IEC 14443(A)")); + PrintAndLogEx(SUCCESS, "BAC...................: %s", BAC ? _GREEN_("Enforced") : _RED_("Not enforced")); + PrintAndLogEx(SUCCESS, "Authentication result.: %s", auth_result ? _GREEN_("Successful") : _RED_("Failed")); + + if (!auth_result) { + DropField(); + return PM3_ESOFT; + } + + // Select EF_DG1 + if (emrtd_select_and_read(response, &resplen, EMRTD_EF_DG1, ks_enc, ks_mac, ssc, BAC, use_14b) == false) { + PrintAndLogEx(ERR, "Failed to read EF_DG1."); + DropField(); + return PM3_ESOFT; + } + + // MRZ on TD1 is 90 characters, 30 on each row. + // MRZ on TD3 is 88 characters, 44 on each row. + char mrz[90] = { 0x00 }; + int mrzlen = 0; + + if (!emrtd_lds_get_data_by_tag(response, &resplen, (uint8_t *) mrz, &mrzlen, 0x5f, 0x1f, true)) { + PrintAndLogEx(ERR, "Failed to read MRZ from EF_DG1."); + DropField(); + return PM3_ESOFT; + } + + // Determine and print the document type + if (mrz[0] == 'I' && mrz[1] == 'P') { + td_variant = 1; + PrintAndLogEx(SUCCESS, "Document Type.........: " _YELLOW_("Passport Card")); + } else if (mrz[0] == 'I') { + td_variant = 1; + PrintAndLogEx(SUCCESS, "Document Type.........: " _YELLOW_("ID Card")); + } else if (mrz[0] == 'P') { + td_variant = 3; + PrintAndLogEx(SUCCESS, "Document Type.........: " _YELLOW_("Passport")); + } else { + td_variant = 1; + PrintAndLogEx(SUCCESS, "Document Type.........: " _YELLOW_("Unknown")); + PrintAndLogEx(INFO, "Assuming ID-style MRZ."); + } + PrintAndLogEx(SUCCESS, "Document Form Factor..: " _YELLOW_("TD%i"), td_variant); + + // Print the MRZ + if (td_variant == 1) { + PrintAndLogEx(DEBUG, "MRZ Row 1: " _YELLOW_("%.30s"), mrz); + PrintAndLogEx(DEBUG, "MRZ Row 2: " _YELLOW_("%.30s"), mrz + 30); + PrintAndLogEx(DEBUG, "MRZ Row 3: " _YELLOW_("%.30s"), mrz + 60); + } else if (td_variant == 3) { + PrintAndLogEx(DEBUG, "MRZ Row 1: " _YELLOW_("%.44s"), mrz); + PrintAndLogEx(DEBUG, "MRZ Row 2: " _YELLOW_("%.44s"), mrz + 44); + } + + PrintAndLogEx(SUCCESS, "Issuing state.........: " _YELLOW_("%.3s"), mrz + 2); + + if (td_variant == 3) { + // Passport form factor + PrintAndLogEx(SUCCESS, "Nationality...........: " _YELLOW_("%.3s"), mrz + 44 + 10); + emrtd_print_name(mrz, 5, 38); + emrtd_print_document_number(mrz, 44); + emrtd_print_dob(mrz, 44 + 13); + emrtd_print_legal_sex(&mrz[44 + 20]); + emrtd_print_expiry(mrz, 44 + 21); + emrtd_print_optional_elements(mrz, 44 + 28, 14, true); + + // Calculate and verify composite check digit + char composite_check_data[50] = { 0x00 }; + memcpy(composite_check_data, mrz + 44, 10); + memcpy(composite_check_data + 10, mrz + 44 + 13, 7); + memcpy(composite_check_data + 17, mrz + 44 + 21, 23); + + if (!emrtd_compare_check_digit(composite_check_data, 39, mrz[87])) { + PrintAndLogEx(SUCCESS, _RED_("Composite check digit is invalid.")); + } + } else if (td_variant == 1) { + // ID form factor + PrintAndLogEx(SUCCESS, "Nationality...........: " _YELLOW_("%.3s"), mrz + 30 + 15); + emrtd_print_name(mrz, 60, 30); + emrtd_print_document_number(mrz, 5); + emrtd_print_dob(mrz, 30); + emrtd_print_legal_sex(&mrz[30 + 7]); + emrtd_print_expiry(mrz, 30 + 8); + emrtd_print_optional_elements(mrz, 15, 15, false); + emrtd_print_optional_elements(mrz, 30 + 18, 11, false); + + // Calculate and verify composite check digit + if (!emrtd_compare_check_digit(mrz, 59, mrz[59])) { + PrintAndLogEx(SUCCESS, _RED_("Composite check digit is invalid.")); + } + } + + DropField(); + return PM3_SUCCESS; +} + +static int cmd_hf_emrtd_dump(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf emrtd dump", + "Dump all files on an eMRTD", + "hf emrtd dump" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("n", "documentnumber", "", "document number, up to 9 chars"), + arg_str0("d", "dateofbirth", "", "date of birth in YYMMDD format"), + arg_str0("e", "expiry", "", "expiry in YYMMDD format"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + uint8_t docnum[10] = { 0x00 }; + uint8_t dob[7] = { 0x00 }; + uint8_t expiry[7] = { 0x00 }; + bool BAC = true; + int slen = 0; // unused + // Go through all args, if even one isn't supplied, mark BAC as unavailable + if (CLIParamStrToBuf(arg_get_str(ctx, 1), docnum, 9, &slen) != 0 || slen == 0) { + BAC = false; + } else { + if (slen != 9) { + // Pad to 9 with < + memset(docnum + slen, 0x3c, 9 - slen); + } + } + + if (CLIParamStrToBuf(arg_get_str(ctx, 2), dob, 6, &slen) != 0 || slen == 0) { + BAC = false; + } + + if (CLIParamStrToBuf(arg_get_str(ctx, 3), expiry, 6, &slen) != 0 || slen == 0) { + BAC = false; + } + + CLIParserFree(ctx); + return dumpHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC); +} + +static int cmd_hf_emrtd_info(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf emrtd info", + "Display info about an eMRTD", + "hf emrtd info" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("n", "documentnumber", "", "document number, up to 9 chars"), + arg_str0("d", "dateofbirth", "", "date of birth in YYMMDD format"), + arg_str0("e", "expiry", "", "expiry in YYMMDD format"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + uint8_t docnum[10] = { 0x00 }; + uint8_t dob[7] = { 0x00 }; + uint8_t expiry[7] = { 0x00 }; + bool BAC = true; + int slen = 0; // unused + // Go through all args, if even one isn't supplied, mark BAC as unavailable + if (CLIParamStrToBuf(arg_get_str(ctx, 1), docnum, 9, &slen) != 0 || slen == 0) { + BAC = false; + } else { + if ( slen != 9) { + memset(docnum + slen, 0x3c, 9 - slen); + } + } + + if (CLIParamStrToBuf(arg_get_str(ctx, 2), dob, 6, &slen) != 0 || slen == 0) { + BAC = false; + } + + if (CLIParamStrToBuf(arg_get_str(ctx, 3), expiry, 6, &slen) != 0 || slen == 0) { + BAC = false; + } + + CLIParserFree(ctx); + return infoHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC); +} + +static int cmd_hf_emrtd_list(const char *Cmd) { + char args[128] = {0}; + if (strlen(Cmd) == 0) { + snprintf(args, sizeof(args), "-t 7816"); + } else { + strncpy(args, Cmd, sizeof(args) - 1); + } + return CmdTraceList(args); +} + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"dump", cmd_hf_emrtd_dump, IfPm3Iso14443, "Dump eMRTD files to binary files"}, + {"info", cmd_hf_emrtd_info, IfPm3Iso14443, "Display info about an eMRTD"}, + {"list", cmd_hf_emrtd_list, AlwaysAvailable, "List ISO 14443A/7816 history"}, + {NULL, NULL, NULL, NULL} +}; + +static int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} + +int CmdHFeMRTD(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} diff --git a/client/src/cmdhfemrtd.h b/client/src/cmdhfemrtd.h new file mode 100644 index 000000000..f19a71ac0 --- /dev/null +++ b/client/src/cmdhfemrtd.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2020 A. Ozkal +// +// 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. +//----------------------------------------------------------------------------- +// High frequency Electronic Machine Readable Travel Document commands +//----------------------------------------------------------------------------- + +#ifndef CMDHFEMRTD_H__ +#define CMDHFEMRTD_H__ + +#include "common.h" + +int CmdHFeMRTD(const char *Cmd); + +int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available); +int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available); +#endif diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 075a5562e..0d182d9c3 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -624,7 +624,7 @@ static int CmdHFiClassReader(const char *Cmd) { CLIParserInit(&ctx, "hf iclass reader", "Act as a iCLASS reader. Look for iCLASS tags until Enter or the pm3 button is pressed", "hf iclass reader -@ -> continuous reader mode" - ); + ); void *argtable[] = { arg_param_begin, @@ -1700,7 +1700,7 @@ static int CmdHFiClassRestore(const char *Cmd) { "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\n" "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 -k 1122334455667788 --elite\n" - ); + ); void *argtable[] = { arg_param_begin, @@ -3318,12 +3318,12 @@ static int CmdHFiClassPermuteKey(const char *Cmd) { static int CmdHFiClassEncode(const char *Cmd) { - CLIParserContext *ctx; + CLIParserContext *ctx; CLIParserInit(&ctx, "hf iclass encode", "Encode binary wiegand to block 7", "hf iclass encode --bin 10001111100000001010100011 --ki 0 -> FC 31 CN 337\n" "hf iclass encode --bin 10001111100000001010100011 --ki 0 --elite -> FC 31 CN 337, writing w elite key" - ); + ); void *argtable[] = { arg_param_begin, @@ -3422,7 +3422,7 @@ static int CmdHFiClassEncode(const char *Cmd) { } // add binary sentinel bit. pushBit(&bout, 1); - + // convert binary string to hex bytes for (int i = 0; i < bin_len; i++) { char c = bin[i]; @@ -3449,11 +3449,11 @@ static int CmdHFiClassEncode(const char *Cmd) { int isok = PM3_SUCCESS; // write - for (uint8_t i=0; i<4; i++) { - isok = iclass_write_block(6 + i, credential + (i*8), key, use_credit_key, elite, rawkey, false, false, auth); + for (uint8_t i = 0; i < 4; i++) { + isok = iclass_write_block(6 + i, credential + (i * 8), key, use_credit_key, elite, rawkey, false, false, auth); switch (isok) { case PM3_SUCCESS: - PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _GREEN_("ok") " ) --> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i*8), 8)); + PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _GREEN_("ok") " ) --> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i * 8), 8)); break; default: PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _RED_("fail") " )", 6 + i, 6 + i); diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 46d7bf0cf..ab41bcf41 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -276,7 +276,7 @@ static int CmdHFMFPInfo(const char *Cmd) { arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); PrintAndLogEx(INFO, "-------------------------------------------------------------"); diff --git a/client/src/cmdlfawid.c b/client/src/cmdlfawid.c index 6591e1364..c54c3cd1d 100644 --- a/client/src/cmdlfawid.c +++ b/client/src/cmdlfawid.c @@ -510,7 +510,7 @@ static int CmdAWIDBrute(const char *Cmd) { break; } - + // truncate card number if ((cn & 0xFFFF) != cn) { cn &= 0xFFFF; @@ -550,7 +550,7 @@ static int CmdAWIDBrute(const char *Cmd) { if (cn > 1) { if (down > 1) { if (sendTry(fmtlen, fc, --down, delay, bits, size, verbose) != PM3_SUCCESS) { - return PM3_ESOFT; + return PM3_ESOFT; } } } diff --git a/client/src/cmdlfdestron.c b/client/src/cmdlfdestron.c index ec5e67a28..13f7a6398 100644 --- a/client/src/cmdlfdestron.c +++ b/client/src/cmdlfdestron.c @@ -190,9 +190,9 @@ static int CmdDestronClone(const char *Cmd) { blocks[1] = (blocks[1] & 0xFFFF) | 0xAAE20000; PrintAndLogEx(INFO, "Preparing to clone Destron tag to " _YELLOW_("%s") " with ID: " _YELLOW_("%s") - , cardtype - , sprint_hex_inrow(data, datalen) - ); + , cardtype + , sprint_hex_inrow(data, datalen) + ); print_blocks(blocks, ARRAYLEN(blocks)); diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index 790e3e070..c8b11a9db 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -118,7 +118,7 @@ void printEM410x(uint32_t hi, uint64_t id, bool verbose) { if (hi) { PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%06X%016" PRIX64), hi, id); } else { - PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%010" PRIX64), id); + PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%010" PRIX64), id); } return; } @@ -395,7 +395,7 @@ static int CmdEM410xReader(const char *Cmd) { // emulate an EM410X tag static int CmdEM410xSim(const char *Cmd) { - + CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 410x sim", "Enables simulation of EM 410x card.\n" @@ -433,7 +433,7 @@ static int CmdEM410xSim(const char *Cmd) { 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", @@ -452,10 +452,10 @@ static int CmdEM410xBrute(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, false); - // clock default 64 in EM410x + // clock default 64 in EM410x uint32_t clk = arg_get_u32_def(ctx, 1, 64); - // default pause time: 1 second + // default pause time: 1 second uint32_t delay = arg_get_u32_def(ctx, 2, 1000); int fnlen = 0; @@ -467,7 +467,7 @@ static int CmdEM410xBrute(const char *Cmd) { PrintAndLogEx(ERR, "Error: Please specify a filename"); return PM3_EINVARG; } - + uint32_t uidcnt = 0; uint8_t stUidBlock = 20; uint8_t *p = NULL; @@ -543,10 +543,10 @@ static int CmdEM410xBrute(const char *Cmd) { memcpy(testuid, uidblock + 5 * c, 5); PrintAndLogEx(INFO, "Bruteforce %d / %d: simulating UID " _YELLOW_("%s") - , c + 1 - , uidcnt - , sprint_hex_inrow(testuid, sizeof(testuid)) - ); + , c + 1 + , uidcnt + , sprint_hex_inrow(testuid, sizeof(testuid)) + ); em410x_construct_emul_graph(testuid, clk); @@ -585,7 +585,7 @@ static int CmdEM410xClone(const char *Cmd) { 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 -> write id to T55x7 tag\n" "lf em 410x clone --id 0F0368568B --q5 -> write id to Q5/T5555 tag" ); @@ -598,7 +598,7 @@ static int CmdEM410xClone(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, false); - // clock default 64 in EM410x + // clock default 64 in EM410x uint32_t clk = arg_get_u32_def(ctx, 1, 64); int uid_len = 0; uint8_t uid[5] = {0}; diff --git a/client/src/cmdlfem4x05.c b/client/src/cmdlfem4x05.c index 0f968d23d..c0dc8c2ee 100644 --- a/client/src/cmdlfem4x05.c +++ b/client/src/cmdlfem4x05.c @@ -773,7 +773,7 @@ int CmdEM4x05Write(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_int0("a", "addr", "", "memory address to write to. (0-13)"), - arg_str1("d", "data", "", "data to write, 4 bytes hex"), + arg_str1("d", "data", "", "data to write, 4 bytes hex"), arg_str0("p", "pwd", "", "optional - password, 4 bytes hex"), arg_lit0(NULL, "po", "protect operation"), arg_param_end @@ -784,14 +784,14 @@ int CmdEM4x05Write(const char *Cmd) { uint64_t inputpwd = arg_get_u64_hexstr_def(ctx, 3, 0xFFFFFFFFFFFFFFFF); bool protect_operation = arg_get_lit(ctx, 4); CLIParserFree(ctx); - + if ((addr > 13) && (protect_operation == false)) { PrintAndLogEx(WARNING, "Address must be between 0 and 13"); return PM3_EINVARG; } - bool use_pwd = false; - uint32_t pwd = ( inputpwd != 0xFFFFFFFFFFFFFFFF) ? (inputpwd & 0xFFFFFFFF) : 0; + bool use_pwd = false; + uint32_t pwd = (inputpwd != 0xFFFFFFFFFFFFFFFF) ? (inputpwd & 0xFFFFFFFF) : 0; if (pwd == 0xFFFFFFFF) { if (protect_operation) PrintAndLogEx(INFO, "Writing protection words data %08X", data); @@ -807,14 +807,14 @@ int CmdEM4x05Write(const char *Cmd) { int res = PM3_SUCCESS; // set Protect Words - if (protect_operation) { + if (protect_operation) { res = em4x05_protect(pwd, use_pwd, data); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } } else { res = em4x05_write_word_ext(addr, pwd, use_pwd, data); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } } @@ -888,25 +888,25 @@ int CmdEM4x05Wipe(const char *Cmd) { bool use_pwd = false; uint32_t pwd = 0; - if ( inputpwd != 0xFFFFFFFFFFFFFFFF) { + if (inputpwd != 0xFFFFFFFFFFFFFFFF) { pwd = (inputpwd & 0xFFFFFFFF); use_pwd = true; } // block 0 : User Data or Chip Info int res = em4x05_write_word_ext(0, pwd, use_pwd, chip_info); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } // block 1 : UID - this should be read only for EM4205 and EM4305 not sure about others res = em4x05_write_word_ext(1, pwd, use_pwd, chip_UID); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { PrintAndLogEx(INFO, "UID block write failed"); } // block 2 : password res = em4x05_write_word_ext(2, pwd, use_pwd, block_data); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } @@ -914,20 +914,20 @@ int CmdEM4x05Wipe(const char *Cmd) { pwd = block_data; // block 3 : user data res = em4x05_write_word_ext(3, pwd, use_pwd, block_data); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } // block 4 : config res = em4x05_write_word_ext(4, pwd, use_pwd, config); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } // Remainder of user/data blocks for (addr = 5; addr < 14; addr++) {// Clear user data blocks res = em4x05_write_word_ext(addr, pwd, use_pwd, block_data); - if ( res != PM3_SUCCESS) { + if (res != PM3_SUCCESS) { return res; } } @@ -2146,4 +2146,4 @@ static int CmdHelp(const char *Cmd) { int CmdLFEM4X05(const char *Cmd) { clearCommandBuffer(); return CmdsParse(CommandTable, Cmd); -} \ No newline at end of file +} diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index d57786cb6..2f415c32b 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -28,7 +28,7 @@ static void prepare_result(const uint8_t *data, int fwr, int lwr, em4x50_word_t // restructure received result in "em4x50_word_t" structure for (int i = fwr; i <= lwr; i++) { for (int j = 0; j < 4; j++) { - words[i].byte[j] = data[i * 4 + (3 - j)]; + words[i].byte[j] = data[i * 4 + (3 - j)]; } } } @@ -131,7 +131,7 @@ static int em4x50_load_file(const char *filename, uint8_t *data, size_t data_len 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")) @@ -202,7 +202,7 @@ int CmdEM4x50ELoad(const char *Cmd) { // 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; } @@ -236,7 +236,7 @@ int CmdEM4x50ESave(const char *Cmd) { 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); @@ -244,7 +244,7 @@ int CmdEM4x50ESave(const char *Cmd) { PrintAndLogEx(WARNING, "No valid em4x50 data in flash memory."); return PM3_ENODATA; } - + // user supplied filename? if (fnlen == 0) { PrintAndLogEx(INFO, "Using UID as filename"); @@ -314,7 +314,7 @@ int CmdEM4x50Login(const char *Cmd) { arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); @@ -323,7 +323,7 @@ int CmdEM4x50Login(const char *Cmd) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes"); return PM3_EINVARG; - } + } uint32_t password = BYTES2UINT32(pwd); @@ -357,30 +357,30 @@ int CmdEM4x50Brute(const char *Cmd) { arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, true); int first_len = 0; - uint8_t first[4] = {0,0,0,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}; + 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; - } + } + 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; + const int speed = 27; // print some information int no_iter = etd.password2 - etd.password1 + 1; @@ -390,10 +390,10 @@ int CmdEM4x50Brute(const char *Cmd) { 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 - ); + , no_iter + , etd.password1 + , etd.password2 + ); PrintAndLogEx(INFO, "Estimated duration: %ih%im%is", dur_h, dur_m, dur_s); // start @@ -432,7 +432,7 @@ int CmdEM4x50Chk(const char *Cmd) { 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; @@ -452,17 +452,17 @@ int CmdEM4x50Chk(const char *Cmd) { 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; + PacketResponseNG resp; int bytes_remaining = datalen; while (bytes_remaining > 0) { - + PrintAndLogEx(INPLACE, "Remaining keys: %i ", bytes_remaining / 4); // upload to flash. @@ -476,7 +476,7 @@ int CmdEM4x50Chk(const char *Cmd) { 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; @@ -486,7 +486,7 @@ int CmdEM4x50Chk(const char *Cmd) { } PrintAndLogEx(NORMAL, ""); - + // print response if (status == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Key " _GREEN_("found: %02x %02x %02x %02x"), @@ -494,7 +494,7 @@ int CmdEM4x50Chk(const char *Cmd) { resp.data.asBytes[2], resp.data.asBytes[1], resp.data.asBytes[0] - ); + ); } else { PrintAndLogEx(FAILED, "No key found"); } @@ -578,7 +578,7 @@ int CmdEM4x50Read(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, true); - + int addr = arg_get_int_def(ctx, 1, 0); int pwd_len = 0; uint8_t pwd[4] = {0x0}; @@ -587,7 +587,7 @@ int CmdEM4x50Read(const char *Cmd) { if (addr <= 0 || addr >= EM4X50_NO_WORDS) { return PM3_EINVARG; - } + } em4x50_data_t etd; @@ -629,11 +629,11 @@ int CmdEM4x50Info(const char *Cmd) { CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; - uint8_t pwd[4] = {0x0}; + uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); CLIParserFree(ctx); - em4x50_data_t etd = {.pwd_given = false}; + 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); @@ -652,7 +652,7 @@ int CmdEM4x50Info(const char *Cmd) { return PM3_ETIMEOUT; } - if ( resp.status == PM3_SUCCESS) + if (resp.status == PM3_SUCCESS) print_info_result(resp.data.asBytes); else PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); @@ -708,7 +708,7 @@ int CmdEM4x50Reader(const char *Cmd) { PrintAndLogEx(INFO, _GREEN_(" %s") "| %s", sprint_hex(words[i].byte, 4), r); } - + PrintAndLogEx(INFO, "-------------+-------------"); } } while (cm && !kbd_enter_pressed()); @@ -813,11 +813,11 @@ int CmdEM4x50Write(const char *Cmd) { arg_str0("p", "pwd", "", "password, 4 bytes, lsb"), arg_param_end }; - + CLIExecWithReturn(ctx, Cmd, argtable, true); int addr = arg_get_int_def(ctx, 1, 0); - + int word_len = 0; uint8_t word[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, word, &word_len); @@ -826,18 +826,18 @@ int CmdEM4x50Write(const char *Cmd) { 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; } - em4x50_data_t etd = {.pwd_given = false}; + 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); @@ -895,7 +895,7 @@ int CmdEM4x50WritePwd(const char *Cmd) { 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}; @@ -907,7 +907,7 @@ int CmdEM4x50WritePwd(const char *Cmd) { CLIParserFree(ctx); - em4x50_data_t etd; + em4x50_data_t etd; if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; @@ -940,9 +940,9 @@ int CmdEM4x50WritePwd(const char *Cmd) { } PrintAndLogEx(SUCCESS, "Writing new password %s (%s)" - , sprint_hex_inrow(npwd, sizeof(npwd)) - , _GREEN_("ok") - ); + , sprint_hex_inrow(npwd, sizeof(npwd)) + , _GREEN_("ok") + ); return PM3_SUCCESS; } @@ -961,7 +961,7 @@ int CmdEM4x50Wipe(const char *Cmd) { arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); @@ -977,7 +977,7 @@ int CmdEM4x50Wipe(const char *Cmd) { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; - + // clear password PacketResponseNG resp; clearCommandBuffer(); @@ -996,13 +996,13 @@ int CmdEM4x50Wipe(const char *Cmd) { // 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; @@ -1013,7 +1013,7 @@ int CmdEM4x50Wipe(const char *Cmd) { return PM3_ETIMEOUT; } - if ( resp.status != PM3_SUCCESS) { + if (resp.status != PM3_SUCCESS) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(FAILED, "Wiping data " _RED_("failed")); return PM3_ESOFT; @@ -1044,7 +1044,7 @@ int CmdEM4x50Restore(const char *Cmd) { arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, true); int uidLen = 0; uint8_t uid[4] = {0x0}; @@ -1099,7 +1099,7 @@ int CmdEM4x50Restore(const char *Cmd) { 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)); @@ -1116,7 +1116,7 @@ int CmdEM4x50Restore(const char *Cmd) { } } - PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Done"); return PM3_SUCCESS; } @@ -1160,10 +1160,10 @@ static command_t CommandTable[] = { {"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"}, + {"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"}, + {"restore", CmdEM4x50Restore, IfPm3EM4x50, "restore EM4x50 dump to tag"}, {"sim", CmdEM4x50Sim, IfPm3EM4x50, "simulate EM4x50 tag"}, {"eload", CmdEM4x50ELoad, IfPm3EM4x50, "upload dump of EM4x50 to emulator memory"}, {"esave", CmdEM4x50ESave, IfPm3EM4x50, "save emulator memory to file"}, diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index ae0b3ad22..032daf4b2 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -16,41 +16,78 @@ #include "commonutil.h" #include "em4x70.h" +#define LOCKBIT_0 BITMASK(6) +#define LOCKBIT_1 BITMASK(7) + +#define BYTES2UINT16(x) ((x[1] << 8) | (x[0])) +#define BYTES2UINT32(x) ((x[3] << 24) | (x[2] << 16) | (x[1] << 8) | (x[0])) + +#define INDEX_TO_BLOCK(x) (((32-x)/2)-1) static int CmdHelp(const char *Cmd); -static void print_info_result(uint8_t *data) { +static void print_info_result(const uint8_t *data) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); - PrintAndLogEx(INFO, "-------------------------------------------------------------"); + 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(INFO, "Block | data | info"); + PrintAndLogEx(INFO, "------+----------+-----------------------------"); + + // Print out each section as memory map in datasheet + + // Start with UM2 + for (int i = 0; i < 8; i += 2) { + PrintAndLogEx(INFO, " %2d | %02X %02X | UM2", INDEX_TO_BLOCK(i), data[31 - i], data[31 - 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(INFO, "------+----------+-----------------------------"); + + // Print PIN (will never have data) + for (int i = 8; i < 12; i += 2) { + PrintAndLogEx(INFO, " %2d | -- -- | PIN write only", INDEX_TO_BLOCK(i)); + } + PrintAndLogEx(INFO, "------+----------+-----------------------------"); + + // Print Crypt Key (will never have data) + for (int i = 12; i < 24; i += 2) { + PrintAndLogEx(INFO, " %2d | -- -- | KEY write-only", INDEX_TO_BLOCK(i)); + } + PrintAndLogEx(INFO, "------+----------+-----------------------------"); + + // Print ID + for (int i = 24; i < 28; i += 2) { + PrintAndLogEx(INFO, " %2d | %02X %02X | ID", INDEX_TO_BLOCK(i), data[31 - i], data[31 - i - 1]); + } + PrintAndLogEx(INFO, "------+----------+-----------------------------"); + + // Print UM1 + for (int i = 28; i < 32; i += 2) { + PrintAndLogEx(INFO, " %2d | %02X %02X | UM1", INDEX_TO_BLOCK(i), data[31 - i], data[31 - i - 1]); + } + PrintAndLogEx(INFO, "------+----------+-----------------------------"); + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "Tag ID: %02X %02X %02X %02X", data[7], data[6], data[5], data[4]); + PrintAndLogEx(INFO, "Lockbit 0: %d", (data[3] & LOCKBIT_0) ? 1 : 0); + PrintAndLogEx(INFO, "Lockbit 1: %d", (data[3] & LOCKBIT_1) ? 1 : 0); + PrintAndLogEx(INFO, "Tag is %s.", (data[3] & LOCKBIT_0) ? _RED_("LOCKED") : _GREEN_("UNLOCKED")); 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."); + PrintAndLogEx(WARNING, "(em4x70) Timeout while waiting for reply."); return PM3_ETIMEOUT; } @@ -77,18 +114,18 @@ int CmdEM4x70Info(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "lf em 4x10 info", + CLIParserInit(&ctx, "lf em 4x70 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" - ); + "lf em 4x70 info --par -> adds parity bit to command\n" + ); void *argtable[] = { arg_param_begin, - arg_lit0("p", "parity", "Add parity bit when sending commands"), + arg_lit0(NULL, "par", "Add parity bit when sending commands"), arg_param_end }; @@ -101,7 +138,7 @@ int CmdEM4x70Info(const char *Cmd) { PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) { - PrintAndLogEx(WARNING, "timeout while waiting for reply."); + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } @@ -110,13 +147,312 @@ int CmdEM4x70Info(const char *Cmd) { return PM3_SUCCESS; } - PrintAndLogEx(FAILED, "reading tag " _RED_("failed")); + PrintAndLogEx(FAILED, "Reading " _RED_("Failed")); + return PM3_ESOFT; +} + +int CmdEM4x70Write(const char *Cmd) { + + // write one block/word (16 bits) to the tag at given block address (0-15) + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x70 write", + "Write EM4x70\n", + "lf em 4x70 write -b 15 -d c0de -> write 'c0de' to block 15\n" + "lf em 4x70 write -b 15 -d c0de --par -> adds parity bit to commands\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "par", "Add parity bit when sending commands"), + arg_int1("b", "block", "", "block/word address, dec"), + arg_str1("d", "data", "", "data, 2 bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + etd.parity = arg_get_lit(ctx, 1); + + int addr = arg_get_int(ctx, 2); + + int word_len = 0; + uint8_t word[2] = {0x0}; + CLIGetHexWithReturn(ctx, 3, word, &word_len); + + CLIParserFree(ctx); + + if (addr < 0 || addr >= EM4X70_NUM_BLOCKS) { + PrintAndLogEx(FAILED, "block has to be within range [0, 15]"); + return PM3_EINVARG; + } + + if (word_len != 2) { + PrintAndLogEx(FAILED, "word/data length must be 2 bytes instead of %d", word_len); + return PM3_EINVARG; + } + + etd.address = (uint8_t) addr; + etd.word = BYTES2UINT16(word);; + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_WRITE, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITE, &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, "Writing " _RED_("Failed")); + return PM3_ESOFT; +} + +int CmdEM4x70Unlock(const char *Cmd) { + + // send pin code to device, unlocking it for writing + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x70 unlock", + "Unlock EM4x70 by sending PIN\n" + "Default pin may be:\n" + " AAAAAAAA\n" + " 00000000\n", + "lf em 4x70 unlock -p 11223344 -> Unlock with PIN\n" + "lf em 4x70 unlock -p 11223344 --par -> Unlock with PIN using parity commands\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "par", "Add parity bit when sending commands"), + arg_str1("p", "pin", "", "pin, 4 bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + etd.parity = arg_get_lit(ctx, 1); + + int pin_len = 0; + uint8_t pin[4] = {0x0}; + + CLIGetHexWithReturn(ctx, 2, pin, &pin_len); + + CLIParserFree(ctx); + + if (pin_len != 4) { + PrintAndLogEx(FAILED, "PIN length must be 4 bytes instead of %d", pin_len); + return PM3_EINVARG; + } + + etd.pin = BYTES2UINT32(pin); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_UNLOCK, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_UNLOCK, &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, "Unlocking tag " _RED_("failed")); + return PM3_ESOFT; +} + +int CmdEM4x70Auth(const char *Cmd) { + + // Authenticate transponder + // Send 56-bit random number + pre-computed f(rnd, k) to transponder. + // Transponder will respond with a response + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x70 auth", + "Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n" + " If F(RN) is incorrect based on the tag crypt key, the tag will not respond", + "lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> Test authentication, tag will respond if successful\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "par", "Add parity bit when sending commands"), + arg_str1(NULL, "rnd", "", "Random 56-bit"), + arg_str1(NULL, "frn", "", "F(RN) 28-bit as 4 hex bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + etd.parity = arg_get_lit(ctx, 1); + + int rnd_len = 7; + CLIGetHexWithReturn(ctx, 2, etd.rnd, &rnd_len); + + int frnd_len = 4; + CLIGetHexWithReturn(ctx, 3, etd.frnd, &frnd_len); + + CLIParserFree(ctx); + + if (rnd_len != 7) { + PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len); + return PM3_EINVARG; + } + + if (frnd_len != 4) { + PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frnd_len); + return PM3_EINVARG; + } + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_AUTH, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_AUTH, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + // Response is 20-bit from tag + PrintAndLogEx(INFO, "Tag Auth Response: %02X %02X %02X", resp.data.asBytes[2], resp.data.asBytes[1], resp.data.asBytes[0]); + return PM3_SUCCESS; + } + + PrintAndLogEx(FAILED, "TAG Authentication " _RED_("Failed")); + return PM3_ESOFT; +} + +int CmdEM4x70WritePIN(const char *Cmd) { + + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x70 writepin", + "Write PIN\n", + "lf em 4x70 writepin -p 11223344 -> Write PIN\n" + "lf em 4x70 writepin -p 11223344 --par -> Write PIN using parity commands\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "par", "Add parity bit when sending commands"), + arg_str1("p", "pin", "", "pin, 4 bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + etd.parity = arg_get_lit(ctx, 1); + + int pin_len = 0; + uint8_t pin[4] = {0x0}; + + CLIGetHexWithReturn(ctx, 2, pin, &pin_len); + + CLIParserFree(ctx); + + if (pin_len != 4) { + PrintAndLogEx(FAILED, "PIN length must be 4 bytes instead of %d", pin_len); + return PM3_EINVARG; + } + + etd.pin = BYTES2UINT32(pin); + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_WRITEPIN, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITEPIN, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + print_info_result(resp.data.asBytes); + PrintAndLogEx(INFO, "Writing new PIN: " _GREEN_("SUCCESS")); + return PM3_SUCCESS; + } + + PrintAndLogEx(FAILED, "Writing new PIN: " _RED_("FAILED")); + return PM3_ESOFT; +} + +int CmdEM4x70WriteKey(const char *Cmd) { + + // Write new crypt key to tag + em4x70_data_t etd = {0}; + + CLIParserContext *ctx; + + CLIParserInit(&ctx, "lf em 4x70 writekey", + "Write new 96-bit key to tag\n", + "lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "par", "Add parity bit when sending commands"), + arg_str1("k", "key", "", "Crypt Key as 12 hex bytes"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, true); + + etd.parity = arg_get_lit(ctx, 1); + + int key_len = 12; + CLIGetHexWithReturn(ctx, 2, etd.crypt_key, &key_len); + + CLIParserFree(ctx); + + if (key_len != 12) { + PrintAndLogEx(FAILED, "Crypt key length must be 12 bytes instead of %d", key_len); + return PM3_EINVARG; + } + + clearCommandBuffer(); + SendCommandNG(CMD_LF_EM4X70_WRITEKEY, (uint8_t *)&etd, sizeof(etd)); + + PacketResponseNG resp; + if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITEKEY, &resp, TIMEOUT)) { + PrintAndLogEx(WARNING, "Timeout while waiting for reply."); + return PM3_ETIMEOUT; + } + + if (resp.status) { + PrintAndLogEx(INFO, "Writing new crypt key: " _GREEN_("SUCCESS")); + return PM3_SUCCESS; + } + + PrintAndLogEx(FAILED, "Writing new crypt key: " _RED_("FAILED")); return PM3_ESOFT; } static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - {"info", CmdEM4x70Info, IfPm3EM4x70, "tag information EM4x70"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"}, + {"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"}, + {"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"}, + {"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"}, + {"writepin", CmdEM4x70WritePIN, IfPm3EM4x70, "Write PIN"}, + {"writekey", CmdEM4x70WriteKey, IfPm3EM4x70, "Write Crypt Key"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdlfem4x70.h b/client/src/cmdlfem4x70.h index a529678e7..f0f221b06 100644 --- a/client/src/cmdlfem4x70.h +++ b/client/src/cmdlfem4x70.h @@ -18,6 +18,11 @@ int CmdLFEM4X70(const char *Cmd); int CmdEM4x70Info(const char *Cmd); +int CmdEM4x70Write(const char *Cmd); +int CmdEM4x70Unlock(const char *Cmd); +int CmdEM4x70Auth(const char *Cmd); +int CmdEM4x70WritePIN(const char *Cmd); +int CmdEM4x70WriteKey(const char *Cmd); int em4x70_info(void); bool detect_4x70_block(void); diff --git a/client/src/cmdlffdxb.c b/client/src/cmdlffdxb.c index f44182483..529f63019 100644 --- a/client/src/cmdlffdxb.c +++ b/client/src/cmdlffdxb.c @@ -727,7 +727,7 @@ static int CmdFdxBClone(const char *Cmd) { } uint32_t extended = 0; - bool has_extended = false; + bool has_extended = false; if (extended_len) { extended = bytes_to_num(edata, extended_len); has_extended = true; @@ -773,10 +773,10 @@ static int CmdFdxBClone(const char *Cmd) { free(bs); PrintAndLogEx(INFO, "Preparing to clone FDX-B to " _YELLOW_("%s") " with animal ID: " _GREEN_("%04u-%"PRIu64) - , cardtype - , country_code - , national_code - ); + , cardtype + , country_code + , national_code + ); print_blocks(blocks, ARRAYLEN(blocks)); int res; @@ -820,7 +820,7 @@ static int CmdFdxBSim(const char *Cmd) { CLIParserFree(ctx); uint32_t extended = 0; - bool has_extended = false; + bool has_extended = false; if (extended_len) { extended = bytes_to_num(edata, extended_len); has_extended = true; diff --git a/client/src/cmdlfgallagher.c b/client/src/cmdlfgallagher.c index cf0488877..12b6b9b49 100644 --- a/client/src/cmdlfgallagher.c +++ b/client/src/cmdlfgallagher.c @@ -233,7 +233,7 @@ static int CmdGallagherClone(const char *Cmd) { static int CmdGallagherSim(const char *Cmd) { - CLIParserContext *ctx; + CLIParserContext *ctx; CLIParserInit(&ctx, "lf gallagher sim", "Enables simulation of GALLAGHER card with specified card number.\n" "Simulation runs until the button is pressed or another USB command is issued.\n", diff --git a/client/src/cmdlfguard.c b/client/src/cmdlfguard.c index ec49b46c5..929079158 100644 --- a/client/src/cmdlfguard.c +++ b/client/src/cmdlfguard.c @@ -226,10 +226,10 @@ static int CmdGuardClone(const char *Cmd) { free(bs); PrintAndLogEx(INFO, "Preparing to clone Guardall to " _YELLOW_("%s") " with Facility Code: " _GREEN_("%u") " Card Number: " _GREEN_("%u") - , cardtype - , facilitycode - , cardnumber - ); + , cardtype + , facilitycode + , cardnumber + ); print_blocks(blocks, ARRAYLEN(blocks)); int res; @@ -281,9 +281,9 @@ static int CmdGuardSim(const char *Cmd) { } PrintAndLogEx(SUCCESS, "Simulating Guardall Prox - Facility Code: " _YELLOW_("%u") " CardNumber: " _YELLOW_("%u") - , facilitycode - , cardnumber - ); + , facilitycode + , cardnumber + ); // Guard uses: clk: 64, invert: 0, encoding: 2 (ASK Biphase) lf_asksim_t *payload = calloc(1, sizeof(lf_asksim_t) + sizeof(bs)); diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index 3e10def56..108550539 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -60,16 +60,16 @@ static int sendTry(uint8_t format_idx, wiegand_card_t *card, uint32_t delay, boo if (HIDPack(format_idx, card, &packed) == false) { PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format."); - return PM3_ESOFT; + return PM3_ESOFT; } if (verbose) { PrintAndLogEx(INFO, "Trying FC: " _YELLOW_("%u") " CN: " _YELLOW_("%"PRIu64) " Issue level: " _YELLOW_("%u") " OEM: " _YELLOW_("%u") - , card->FacilityCode - , card->CardNumber - , card->IssueLevel - , card->OEM - ); + , card->FacilityCode + , card->CardNumber + , card->IssueLevel + , card->OEM + ); } lf_hidsim_t payload; diff --git a/client/src/cmdlfindala.c b/client/src/cmdlfindala.c index df2bd4f46..da2662af1 100644 --- a/client/src/cmdlfindala.c +++ b/client/src/cmdlfindala.c @@ -250,7 +250,7 @@ static int CmdIndalaDemod(const char *Cmd) { "lf indala demod --clock 32 -> demod a Indala tag from GraphBuffer using a clock of RF/32\n" "lf indala demod --clock 32 -i -> demod a Indala tag from GraphBuffer using a clock of RF/32 and inverting data\n" "lf indala demod --clock 64 -i --maxerror 0 -> demod a Indala tag from GraphBuffer using a clock of RF/64, inverting data and allowing 0 demod errors" - ); + ); void *argtable[] = { arg_param_begin, @@ -281,7 +281,7 @@ static int CmdIndalaDemodAlt(const char *Cmd) { "It's now considered obsolete but remains because it has sometimes its advantages.", "lf indala altdemod\n" "lf indala altdemod --long -> demod a Indala tag from GraphBuffer as 224 bit long format" - ); + ); void *argtable[] = { arg_param_begin, @@ -583,9 +583,9 @@ static int CmdIndalaSim(const char *Cmd) { // lf simpsk 1 c 32 r 2 d 0102030405060708 PrintAndLogEx(SUCCESS, "Simulating " _YELLOW_("%s") " Indala raw " _YELLOW_("%s") - , (is_long_uid) ? "224b" : "64b" - , sprint_hex_inrow(raw, raw_len) - ); + , (is_long_uid) ? "224b" : "64b" + , sprint_hex_inrow(raw, raw_len) + ); PrintAndLogEx(SUCCESS, "Press pm3-button to abort simulation or run another command"); // indala PSK, clock 32, carrier 0 @@ -665,7 +665,7 @@ static int CmdIndalaClone(const char *Cmd) { uint8_t max = 0; uint32_t blocks[8] = {0}; char cardtype[16] = {"T55x7"}; - + if (is_long_uid) { blocks[0] = T55x7_BITRATE_RF_32 | T55x7_MODULATION_PSK2 | (7 << T55x7_MAXBLOCK_SHIFT); @@ -673,7 +673,7 @@ static int CmdIndalaClone(const char *Cmd) { blocks[0] = T5555_FIXED | T5555_SET_BITRATE(32) | T5555_MODULATION_PSK2 | (7 << T5555_MAXBLOCK_SHIFT); snprintf(cardtype, sizeof(cardtype), "Q5/T5555"); } - + if (em) { blocks[0] = EM4305_INDALA_224_CONFIG_BLOCK; snprintf(cardtype, sizeof(cardtype), "EM4305/4469"); @@ -691,9 +691,9 @@ static int CmdIndalaClone(const char *Cmd) { // 224 BIT UID // config for Indala (RF/32;PSK2 with RF/2;Maxblock=7) PrintAndLogEx(INFO, "Preparing to clone Indala 224bit to " _YELLOW_("%s") " raw " _GREEN_("%s") - , cardtype - , sprint_hex_inrow(raw, raw_len) - ); + , cardtype + , sprint_hex_inrow(raw, raw_len) + ); } else { @@ -737,7 +737,7 @@ static int CmdIndalaClone(const char *Cmd) { blocks[0] = T5555_FIXED | T5555_SET_BITRATE(32) | T5555_MODULATION_PSK1 | (2 << T5555_MAXBLOCK_SHIFT); snprintf(cardtype, sizeof(cardtype), "Q5/T5555"); } - + if (em) { blocks[0] = EM4305_INDALA_64_CONFIG_BLOCK; snprintf(cardtype, sizeof(cardtype), "EM4305/4469"); @@ -749,9 +749,9 @@ static int CmdIndalaClone(const char *Cmd) { // config for Indala 64 format (RF/32;PSK1 with RF/2;Maxblock=2) PrintAndLogEx(INFO, "Preparing to clone Indala 64bit to " _YELLOW_("%s") " raw " _GREEN_("%s") - , cardtype - , sprint_hex_inrow(raw, raw_len) - ); + , cardtype + , sprint_hex_inrow(raw, raw_len) + ); } print_blocks(blocks, max); diff --git a/client/src/cmdlfio.c b/client/src/cmdlfio.c index 3adc0ed90..fd9ce6d1d 100644 --- a/client/src/cmdlfio.c +++ b/client/src/cmdlfio.c @@ -318,12 +318,12 @@ static int CmdIOProxClone(const char *Cmd) { blocks[2] = bytebits_to_byte(bits + 32, 32); PrintAndLogEx(INFO, "Preparing to clone ioProx to " _YELLOW_("%s") " with Version: " _GREEN_("%u") " FC: " _GREEN_("%u (0x%02x)") " CN: " _GREEN_("%u") - , cardtype - , version - , fc - , fc - , cn - ); + , cardtype + , version + , fc + , fc + , cn + ); print_blocks(blocks, ARRAYLEN(blocks)); int res; diff --git a/client/src/cmdlfjablotron.c b/client/src/cmdlfjablotron.c index 7e1f81557..6e8a35d2c 100644 --- a/client/src/cmdlfjablotron.c +++ b/client/src/cmdlfjablotron.c @@ -219,11 +219,11 @@ static int CmdJablotronClone(const char *Cmd) { free(bits); - uint64_t id = getJablontronCardId(fullcode); + uint64_t id = getJablontronCardId(fullcode); PrintAndLogEx(INFO, "Preparing to clone Jablotron to " _YELLOW_("%s") " with FullCode: " _GREEN_("%"PRIx64)" id: " _GREEN_("%"PRIx64), cardtype, fullcode, id); print_blocks(blocks, ARRAYLEN(blocks)); - + int res; if (em) { res = em4x05_clone_tag(blocks, ARRAYLEN(blocks), 0, false); diff --git a/client/src/cmdlfmotorola.c b/client/src/cmdlfmotorola.c index 3f2ce059c..336bdf7b2 100644 --- a/client/src/cmdlfmotorola.c +++ b/client/src/cmdlfmotorola.c @@ -264,7 +264,7 @@ static int CmdMotorolaSim(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, true); CLIParserFree(ctx); - + // PSK sim. PrintAndLogEx(INFO, " PSK1 at 66 kHz... Interesting."); PrintAndLogEx(INFO, " To be implemented, feel free to contribute!"); diff --git a/client/src/cmdlfnedap.c b/client/src/cmdlfnedap.c index 1e87c85bf..20cb5eec5 100644 --- a/client/src/cmdlfnedap.c +++ b/client/src/cmdlfnedap.c @@ -145,13 +145,13 @@ int demodNedap(bool verbose) { badgeId = r1 * 10000 + r2 * 1000 + r3 * 100 + r4 * 10 + r5; PrintAndLogEx(SUCCESS, "NEDAP (%s) - ID: " _YELLOW_("%05u") " subtype: " _YELLOW_("%1u")" customer code: " _YELLOW_("%u / 0x%03X") " Raw: " _YELLOW_("%s") - , (size == 128) ? "128b" : "64b" - , badgeId - , subtype - , customerCode - , customerCode - , sprint_hex_inrow(data, size / 8) - ); + , (size == 128) ? "128b" : "64b" + , badgeId + , subtype + , customerCode + , customerCode + , sprint_hex_inrow(data, size / 8) + ); PrintAndLogEx(DEBUG, "Checksum (%s) 0x%04X", _GREEN_("ok"), checksum); } else { @@ -373,7 +373,7 @@ static int CmdLFNedapClone(const char *Cmd) { arg_u64_0(NULL, "st", "", "optional - sub type (default 5)"), arg_u64_1(NULL, "cc", "", "customer code (0-4095)"), arg_u64_1(NULL, "id", "", "ID (0-99999)"), - arg_lit0("l", "long", "optional - long (128), default to short (64)"), + arg_lit0("l", "long", "optional - long (128), default to short (64)"), arg_lit0(NULL, "q5", "optional - specify writing to Q5/T5555 tag"), arg_lit0(NULL, "em", "optional - specify writing to EM4305/4469 tag"), arg_param_end @@ -395,17 +395,17 @@ static int CmdLFNedapClone(const char *Cmd) { } if (sub_type > 0xF) { PrintAndLogEx(FAILED, "out-of-range, valid subtype is between 0-15"); - return PM3_EINVARG; + return PM3_EINVARG; } if (customer_code > 0xFFF) { PrintAndLogEx(FAILED, "out-of-range, valid customer code is between 0-4095"); - return PM3_EINVARG; + return PM3_EINVARG; } if (id > 99999) { PrintAndLogEx(FAILED, "out-of-range, id max value is 99999"); - return PM3_EINVARG; + return PM3_EINVARG; } PrintAndLogEx(SUCCESS, "NEDAP (%s) - ID: " _GREEN_("%05u") " subtype: " _GREEN_("%1u") " customer code: " _GREEN_("%u / 0x%03X") @@ -454,7 +454,7 @@ static int CmdLFNedapClone(const char *Cmd) { NedapGen(sub_type, customer_code, id, is_long, data); for (uint8_t i = 1; i < max ; i++) { - blocks[i] = bytes_to_num (data + ((i - 1) * 4), 4); + blocks[i] = bytes_to_num(data + ((i - 1) * 4), 4); } PrintAndLogEx(SUCCESS, "Preparing to clone NEDAP to " _YELLOW_("%s") " tag", cardtype); @@ -492,7 +492,7 @@ static int CmdLFNedapSim(const char *Cmd) { arg_u64_0(NULL, "st", "", "optional - sub type (default 5)"), arg_u64_1(NULL, "cc", "", "customer code (0-4095)"), arg_u64_1(NULL, "id", "", "ID (0-99999)"), - arg_lit0("l", "long", "optional - long (128), default to short (64)"), + arg_lit0("l", "long", "optional - long (128), default to short (64)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -502,20 +502,20 @@ static int CmdLFNedapSim(const char *Cmd) { uint32_t id = arg_get_u32_def(ctx, 3, 0); bool is_long = arg_get_lit(ctx, 4); CLIParserFree(ctx); - + if (sub_type > 0xF) { PrintAndLogEx(FAILED, "out-of-range, valid subtype is between 0-15"); - return PM3_EINVARG; + return PM3_EINVARG; } if (customer_code > 0xFFF) { PrintAndLogEx(FAILED, "out-of-range, valid customer code is between 0-4095"); - return PM3_EINVARG; + return PM3_EINVARG; } if (id > 99999) { PrintAndLogEx(FAILED, "out-of-range, id max value is 99999"); - return PM3_EINVARG; + return PM3_EINVARG; } PrintAndLogEx(SUCCESS, "NEDAP (%s) - ID: " _GREEN_("%05u") " subtype: " _GREEN_("%1u") " customer code: " _GREEN_("%u / 0x%03X") diff --git a/client/src/cmdlfpcf7931.c b/client/src/cmdlfpcf7931.c index 48a029e2b..bd744f8ba 100644 --- a/client/src/cmdlfpcf7931.c +++ b/client/src/cmdlfpcf7931.c @@ -125,7 +125,7 @@ static int CmdLFPCF7931Config(const char *Cmd) { } if (pwd_len) { - memcpy(configPcf.Pwd, pwd, sizeof(configPcf.Pwd)); + memcpy(configPcf.Pwd, pwd, sizeof(configPcf.Pwd)); } if (delay != -1) { configPcf.InitDelay = (delay & 0xFFFF); @@ -134,7 +134,7 @@ static int CmdLFPCF7931Config(const char *Cmd) { configPcf.OffsetWidth = (ow & 0xFFFF); } if (op != 0xFFFF) { - configPcf.OffsetPosition =(op & 0xFFFF); + configPcf.OffsetPosition = (op & 0xFFFF); } pcf7931_printConfig(); diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index a3f5ca20b..d67770ba3 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -2806,7 +2806,7 @@ char *GetModelStrFromCID(uint32_t cid) { } char *GetConfigBlock0Source(uint8_t id) { - + static char buf[40]; char *retStr = buf; diff --git a/client/src/cmdlft55xx.h b/client/src/cmdlft55xx.h index 90fbaa85d..d87194b25 100644 --- a/client/src/cmdlft55xx.h +++ b/client/src/cmdlft55xx.h @@ -129,7 +129,7 @@ typedef struct { notSet = 0x00, autoDetect = 0x01, userSet = 0x02, - tagRead = 0x03, + tagRead = 0x03, } block0Status; enum { RF_8 = 0x00, diff --git a/client/src/cmdlfti.c b/client/src/cmdlfti.c index b02ad2e34..3e8138332 100644 --- a/client/src/cmdlfti.c +++ b/client/src/cmdlfti.c @@ -330,7 +330,7 @@ static int CmdTIWrite(const char *Cmd) { arg_param_begin, arg_str1("r", "raw", "", "raw hex data. 8 bytes max"), arg_str0(NULL, "crc", "", "optional - crc"), - arg_param_end + arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -354,7 +354,7 @@ static int CmdTIWrite(const char *Cmd) { payload.crc = bytes_to_num(crc, crc_len); clearCommandBuffer(); - SendCommandNG(CMD_LF_TI_WRITE, (uint8_t*)&payload, sizeof(payload)); + SendCommandNG(CMD_LF_TI_WRITE, (uint8_t *)&payload, sizeof(payload)); PrintAndLogEx(SUCCESS, "Done"); PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`lf ti reader`") " to verify"); return PM3_SUCCESS; diff --git a/client/src/cmdmain.c b/client/src/cmdmain.c index 9b26512f1..4d393756f 100644 --- a/client/src/cmdmain.c +++ b/client/src/cmdmain.c @@ -165,19 +165,35 @@ int CmdRem(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "rem", "Add a text line in log file", - "rem" + "rem my message -> adds a timestamp with `my message`" ); void *argtable[] = { arg_param_begin, + arg_strx1(NULL, NULL, NULL, "message line you want inserted"), arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); - CLIParserFree(ctx); + CLIExecWithReturn(ctx, Cmd, argtable, false); + struct arg_str* foo = arg_get_str(ctx, 1); + size_t count = 0; + size_t len = 0; + do { + count += strlen(foo->sval[len]); + } while (len++ < (foo->count - 1)); + + char s[count + foo->count]; + memset(s, 0, sizeof(s)); + + len = 0; + do { + snprintf(s + strlen(s), sizeof(s) - strlen(s), "%s ", foo->sval[len]); + } while (len++ < (foo->count - 1)); + + CLIParserFree(ctx); char buf[22] = {0}; AppendDate(buf, sizeof(buf), NULL); - PrintAndLogEx(NORMAL, "%s remark: %s", buf, Cmd); + PrintAndLogEx(SUCCESS, "%s remark: %s", buf, s); return PM3_SUCCESS; } diff --git a/client/src/emv/emvcore.c b/client/src/emv/emvcore.c index 7982ff711..f9cbe4f82 100644 --- a/client/src/emv/emvcore.c +++ b/client/src/emv/emvcore.c @@ -617,7 +617,7 @@ int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen) { } int EMVGPO(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) { - return EMVExchange(channel, LeaveFieldON, (sAPDU) {0x80, 0xa8, 0x00, 0x00, PDOLLen, PDOL}, Result, MaxResultLen, ResultLen, sw, tlv); + return EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x80, 0xa8, 0x00, 0x00, PDOLLen, PDOL}, true, Result, MaxResultLen, ResultLen, sw, tlv); } int EMVReadRecord(EMVCommandChannel channel, bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) { diff --git a/client/src/preferences.c b/client/src/preferences.c index 5b5b9bee9..9f507b969 100644 --- a/client/src/preferences.c +++ b/client/src/preferences.c @@ -53,7 +53,7 @@ int preferences_load(void) { session.overlay.h = 200; session.overlay.w = session.plot.w; session.overlay_sliders = true; - session.show_hints = false; + session.show_hints = true; // setDefaultPath (spDefault, ""); // setDefaultPath (spDump, ""); diff --git a/common/commonutil.c b/common/commonutil.c index 70114bba1..802f362d8 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -109,7 +109,7 @@ uint32_t reflect32(uint32_t b) { // swap bytes v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); // swap 2-byte long pairs - v = ( v >> 16 ) | ( v << 16); + v = (v >> 16) | (v << 16); return v; } diff --git a/common/lfdemod.c b/common/lfdemod.c index 24de41676..171417143 100644 --- a/common/lfdemod.c +++ b/common/lfdemod.c @@ -1082,8 +1082,8 @@ int DetectPSKClock(uint8_t *dest, size_t size, int clock, size_t *firstPhaseShif if (g_debugMode == 2) prnt("DEBUG PSK: firstFullWave: %zu, waveLen: %d", firstFullWave, fullWaveLen); // Avoid autodetect if user selected a clock - for(uint8_t validClk = 1; validClk < 8; validClk++) { - if(clock == clk[validClk]) return(clock); + for (uint8_t validClk = 1; validClk < 8; validClk++) { + if (clock == clk[validClk]) return (clock); } //test each valid clock from greatest to smallest to see which lines up diff --git a/doc/commands.md b/doc/commands.md index 44489d546..7bc9342c5 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -38,7 +38,6 @@ Check column "offline" for their availability. |`analyse nuid `|Y |`create NUID from 7byte UID` |`analyse demodbuff `|Y |`Load binary string to demodbuffer` |`analyse freq `|Y |`Calc wave lengths` -|`analyse foo `|Y |`muxer` ### data @@ -143,6 +142,7 @@ Check column "offline" for their availability. |`hf 14a raw `|N |`Send raw hex data to tag` |`hf 14a antifuzz `|N |`Fuzzing the anticollision phase. Warning! Readers may react strange` |`hf 14a config `|N |`Configure 14a settings (use with caution)` +|`hf 14a apdufind `|N |`Enuerate APDUs - CLA/INS/P1P2` ### hf 14b @@ -248,28 +248,29 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- -|`hf iclass help `|Y |`This help` -|`hf iclass dump `|N |`[options..] Dump Picopass / iCLASS tag to file` -|`hf iclass info `|Y |` Tag information` -|`hf iclass list `|Y |` List iclass history` -|`hf iclass rdbl `|N |`[options..] Read Picopass / iCLASS block` -|`hf iclass reader `|N |` Act like an Picopass / iCLASS reader` -|`hf iclass restore `|N |`[options..] Restore a dump file onto a Picopass / iCLASS tag` -|`hf iclass sniff `|N |` Eavesdrop Picopass / iCLASS communication` -|`hf iclass wrbl `|N |`[options..] Write Picopass / iCLASS block` -|`hf iclass chk `|N |`[options..] Check keys` -|`hf iclass loclass `|Y |`[options..] Use loclass to perform bruteforce reader attack` -|`hf iclass lookup `|Y |`[options..] Uses authentication trace to check for key in dictionary file` -|`hf iclass sim `|N |`[options..] Simulate iCLASS tag` -|`hf iclass eload `|N |`[f ] Load Picopass / iCLASS dump file into emulator memory` -|`hf iclass esave `|N |`[f ] Save emulator memory to file` -|`hf iclass eview `|N |`[options..] View emulator memory` -|`hf iclass calcnewkey `|Y |`[options..] Calc diversified keys (blocks 3 & 4) to write new keys` -|`hf iclass encrypt `|Y |`[options..] Encrypt given block data` -|`hf iclass decrypt `|Y |`[options..] Decrypt given block data or tag dump file` -|`hf iclass managekeys `|Y |`[options..] Manage keys to use with iclass commands` -|`hf iclass permutekey `|N |` Permute function from 'heart of darkness' paper` -|`hf iclass view `|Y |`[options..] Display content from tag dump file` +|`hf iclass help `|Y |` This help` +|`hf iclass dump `|N |`[*] Dump Picopass / iCLASS tag to file` +|`hf iclass info `|Y |` Tag information` +|`hf iclass list `|Y |` List iclass history` +|`hf iclass rdbl `|N |`[*] Read Picopass / iCLASS block` +|`hf iclass reader `|N |` Act like an Picopass / iCLASS reader` +|`hf iclass restore `|N |`[*] Restore a dump file onto a Picopass / iCLASS tag` +|`hf iclass sniff `|N |` Eavesdrop Picopass / iCLASS communication` +|`hf iclass wrbl `|N |`[*] Write Picopass / iCLASS block` +|`hf iclass chk `|N |`[*] Check keys` +|`hf iclass loclass `|Y |`[*] Use loclass to perform bruteforce reader attack` +|`hf iclass lookup `|Y |`[*] Uses authentication trace to check for key in dictionary file` +|`hf iclass sim `|N |`[*] Simulate iCLASS tag` +|`hf iclass eload `|N |`[*] Load Picopass / iCLASS dump file into emulator memory` +|`hf iclass esave `|N |`[*] Save emulator memory to file` +|`hf iclass eview `|N |`[.] View emulator memory` +|`hf iclass calcnewkey `|Y |`[*] Calc diversified keys (blocks 3 & 4) to write new keys` +|`hf iclass encode `|Y |`[*] Encode binary wiegand to block 7` +|`hf iclass encrypt `|Y |`[*] Encrypt given block data` +|`hf iclass decrypt `|Y |`[*] Decrypt given block data or tag dump file` +|`hf iclass managekeys `|Y |`[*] Manage keys to use with iclass commands` +|`hf iclass permutekey `|N |` Permute function from 'heart of darkness' paper` +|`hf iclass view `|Y |`[*] Display content from tag dump file` ### hf legic @@ -577,10 +578,10 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- |`lf em help `|Y |`This help` -|`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 em 410x `|Y |`EM 4102 commands...` +|`lf em 4x05 `|Y |`EM 4205 / 4305 / 4369 / 4469 commands...` +|`lf em 4x50 `|Y |`EM 4350 / 4450 commands...` +|`lf em 4x70 `|Y |`EM 4070 / 4170 commands...` ### lf fdxb @@ -672,9 +673,9 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- |`lf indala help `|Y |`this help` -|`lf indala demod `|Y |`demodulate an indala tag (PSK1) from GraphBuffer` -|`lf indala altdemod `|Y |`alternative method to Demodulate samples for Indala 64 bit UID (option '224' for 224 bit)` -|`lf indala reader `|N |`read an Indala Prox tag from the antenna` +|`lf indala demod `|Y |`demodulate an Indala tag (PSK1) from GraphBuffer` +|`lf indala altdemod `|Y |`alternative method to demodulate samples for Indala 64 bit UID (option '224' for 224 bit)` +|`lf indala reader `|N |`read an Indala tag from the antenna` |`lf indala clone `|N |`clone Indala tag to T55x7 or Q5/T5555` |`lf indala sim `|N |`simulate Indala tag` @@ -686,10 +687,10 @@ Check column "offline" for their availability. |command |offline |description |------- |------- |----------- |`lf io help `|Y |`this help` -|`lf io demod `|Y |`demodulate an IOProx tag from the GraphBuffer` +|`lf io demod `|Y |`demodulate an ioProx tag from the GraphBuffer` |`lf io reader `|N |`attempt to read and extract tag data` -|`lf io clone `|N |`clone IOProx tag to T55x7 or Q5/T5555` -|`lf io sim `|N |`simulate IOProx tag` +|`lf io clone `|N |`clone ioProx tag to T55x7 or Q5/T5555` +|`lf io sim `|N |`simulate ioProx tag` |`lf io watch `|N |`continuously watch for cards. Reader mode` @@ -1001,7 +1002,7 @@ Check column "offline" for their availability. |------- |------- |----------- |`wiegand help `|Y |`This help` |`wiegand list `|Y |`List available wiegand formats` -|`wiegand encode `|Y |`Encode to wiegand raw hex` -|`wiegand decode `|Y |`Convert raw hex to decoded wiegand format` +|`wiegand encode `|Y |`Encode to wiegand raw hex (currently for HID Prox)` +|`wiegand decode `|Y |`Convert raw hex to decoded wiegand format (currently for HID Prox)` diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index 4f9cb2f68..9e4cdce12 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -426,6 +426,12 @@ Note: it seems some cards only accept the "change UID" command. It accepts direct read of block0 (and only block0) without prior auth. +Writing to block 0 has some side-effects: + +* It changes also the UID. Changing the UID *does not* change block 0. +* ATQA and SAK bytes are automatically replaced by fixed values. +* On 4-byte UID cards, BCC byte is automatically corrected. + ### Characteristics * UID: 4b and 7b versions @@ -452,6 +458,8 @@ Equivalent: ``` # change just UID: hf 14a raw -s -c -t 2000 90FBCCCC07 11223344556677 +# read block0: +hf 14a raw -s -c 3000 # write block0: hf 14a raw -s -c -t 2000 90F0CCCC10 041219c3219316984200e32000000000 # lock (uid/block0?) forever: diff --git a/include/em4x70.h b/include/em4x70.h index 503b5f2e8..183e7398d 100644 --- a/include/em4x70.h +++ b/include/em4x70.h @@ -11,8 +11,29 @@ #ifndef EM4X70_H__ #define EM4X70_H__ +#define EM4X70_NUM_BLOCKS 16 + +// Common word/block addresses +#define EM4X70_PIN_WORD_LOWER 10 +#define EM4X70_PIN_WORD_UPPER 11 + typedef struct { bool parity; + + // Used for writing address + uint8_t address; + uint16_t word; + + // PIN to unlock + uint32_t pin; + + // Used for authentication + uint8_t rnd[7]; + uint8_t frnd[4]; + + // Used to write new key + uint8_t crypt_key[12]; + } em4x70_data_t; #endif /* EM4X70_H__ */ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 217a468e2..e3dc29aee 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -517,6 +517,11 @@ typedef struct { #define CMD_LF_EM4X50_ESET 0x0252 #define CMD_LF_EM4X50_CHK 0x0253 #define CMD_LF_EM4X70_INFO 0x0260 +#define CMD_LF_EM4X70_WRITE 0x0261 +#define CMD_LF_EM4X70_UNLOCK 0x0262 +#define CMD_LF_EM4X70_AUTH 0x0263 +#define CMD_LF_EM4X70_WRITEPIN 0x0264 +#define CMD_LF_EM4X70_WRITEKEY 0x0265 // Sampling configuration for LF reader/sniffer #define CMD_LF_SAMPLING_SET_CONFIG 0x021D #define CMD_LF_FSK_SIMULATE 0x021E