diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd689846c..b19eb73cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
+ - Added `lf em 4x70 autorecover` - writable ID48 tags now have automatic key recovery (@henrygab)
- Set max write blocks to 251 in hf_mf_ultimatecard before writing version/signature in NTAG 21X (@antgamdia)
- Changed HF_UNISNIFF standalone mode, output and also sniffs iclass too (@iceman1001)
- Changed `hw standalone` - added `-b` param to control HF_UNISNIFF from client (@iceman1001)
diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c
index 40eadb890..1ab415dae 100644
--- a/armsrc/em4x70.c
+++ b/armsrc/em4x70.c
@@ -581,6 +581,11 @@ static bool send_command_and_read(uint8_t command, uint8_t *bytes, size_t length
Dbprintf("Invalid data received length: %d, expected %d", len, out_length_bits);
return false;
}
+ // TODO: Figure out why getting an extra bit (quite often) here
+ // e.g., write block and info commands both reach here and output:
+ // [#] Should have a multiple of 8 bits, was sent 33
+ // [#] Should have a multiple of 8 bits, was sent 65
+ // Extra bits are currently just dropped, with no ill effect noticed.
bits2bytes(bits, len, bytes);
return true;
}
@@ -903,10 +908,12 @@ void em4x70_write_key(const em4x70_data_t *etd, bool ledcontrol) {
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.
+ // The client now has support for test authentication after
+ // writing a new key, thus allowing to verify that the new
+ // key was written correctly. This is what the datasheet
+ // suggests. Not currently implemented in the firmware,
+ // although ID48LIB has no dependencies that would prevent
+ // use within the firmware layer.
}
}
diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c
index daa73ad7b..b9d632703 100644
--- a/client/src/cmdlfem4x70.c
+++ b/client/src/cmdlfem4x70.c
@@ -25,115 +25,427 @@
#include "em4x70.h"
#include "id48.h"
#include "time.h"
+#include "util_posix.h" // msleep()
#define LOCKBIT_0 BITMASK(6)
#define LOCKBIT_1 BITMASK(7)
-#define INDEX_TO_BLOCK(x) (((32-x)/2)-1)
+#define BYTE_ARRAY_INDEX_TO_BLOCK(x) ((31-(x))/2)
+
+// TODO: Optional: use those unique structures in a union, call it em4x70_data_t, but add a first
+// common header field that includes the command itself (to improve debugging / validation).
+typedef struct _em4x70_tag_info_t {
+ ///
+ /// The full data on an em4x70 the tag.
+ /// [31] == Block 15 MSB == UM2₆₃..UM2₅₆
+ /// [30] == Block 15 LSB == UM2₅₅..UM2₄₈
+ /// [29] == Block 14 MSB == UM2₄₇..UM2₄₀
+ /// [28] == Block 14 LSB == UM2₃₉..UM2₃₂
+ /// [27] == Block 13 MSB == UM2₃₁..UM2₂₄
+ /// [26] == Block 13 LSB == UM2₂₃..UM2₁₆
+ /// [25] == Block 12 MSB == UM2₁₅..UM2₀₈
+ /// [24] == Block 12 LSB == UM2₀₇..UM2₀₀
+ /// [23] == Block 11 MSB == Pin₃₁..Pin₂₄
+ /// [22] == Block 11 LSB == Pin₂₃..Pin₁₆
+ /// [21] == Block 10 MSB == Pin₁₅..Pin₀₈
+ /// [20] == Block 10 LSB == Pin₀₇..Pin₀₀
+ /// [19] == Block 9 MSB == Key₉₅..Key₈₈
+ /// [18] == Block 9 LSB == Key₈₇..Key₈₀
+ /// [17] == Block 8 MSB == Key₇₉..Key₇₂
+ /// [16] == Block 8 LSB == Key₇₁..Key₆₄
+ /// [15] == Block 7 MSB == Key₆₃..Key₅₆
+ /// [14] == Block 7 LSB == Key₅₅..Key₄₈
+ /// [13] == Block 6 MSB == Key₄₇..Key₄₀
+ /// [12] == Block 6 LSB == Key₃₉..Key₃₂
+ /// [11] == Block 5 MSB == Key₃₁..Key₂₄
+ /// [10] == Block 5 LSB == Key₂₃..Key₁₆
+ /// [ 9] == Block 4 MSB == Key₁₅..Key₀₈
+ /// [ 8] == Block 4 LSB == Key₀₇..Key₀₀
+ /// [ 7] == Block 3 MSB == ID₃₁..ID₂₄
+ /// [ 6] == Block 3 LSB == ID₂₃..ID₁₆
+ /// [ 5] == Block 2 MSB == ID₁₅..ID₀₈
+ /// [ 4] == Block 2 LSB == ID₀₇..ID₀₀
+ /// [ 3] == Block 1 MSB == L₁ L₀ UM1₂₉..UM1₂₄
+ /// [ 2] == Block 1 LSB == UM1₂₃..UM1₁₆
+ /// [ 1] == Block 0 MSB == UM1₁₅..UM1₀₈
+ /// [ 0] == Block 0 LSB == UM1₀₇..UM1₀₀
+ ///
+ ///
+ /// When moving to C++, strongly consider adding
+ /// const helper functions to extract a given
+ /// block of data into native uint16_t.
+ /// See also the print_info_result() function
+ /// for visual presentation of the data.
+ ///
+ uint8_t Raw[32];
+} em4x70_tag_info_t;
+
+typedef struct _em4x70_cmd_input_info_t {
+ uint8_t use_parity;
+} em4x70_cmd_input_info_t;
+typedef struct _em4x70_cmd_input_writeblock_t {
+ uint8_t use_parity;
+ uint8_t block;
+ uint8_t value[2];
+} em4x70_cmd_input_writeblock_t;
+
+typedef struct _em4x70_cmd_input_brute_t {
+ uint8_t use_parity;
+ ID48LIB_NONCE rn;
+ ID48LIB_FRN frn;
+ uint8_t block;
+ uint8_t partial_key_start[2];
+} em4x70_cmd_input_brute_t;
+typedef struct _em4x70_cmd_output_brute_t {
+ ///
+ /// The returned data is big endian (MSB first).
+ /// For block 9:
+ /// partial_key[0] == Key₉₅..Key₈₈ == Block 9 MSB
+ /// partial_key[1] == Key₈₇..Key₈₀ == Block 9 LSB
+ /// For block 8:
+ /// partial_key[0] == Key₇₉..Key₇₂ == Block 8 MSB
+ /// partial_key[1] == Key₇₁..Key₆₄ == Block 8 LSB
+ /// For block 7:
+ /// partial_key[15] == Key₆₃..Key₅₆ == Block 7 MSB
+ /// partial_key[14] == Key₅₅..Key₄₈ == Block 7 LSB
+ ///
+ uint8_t partial_key[2];
+} em4x70_cmd_output_brute_t;
+
+typedef struct _em4x70_cmd_input_unlock_t {
+ uint8_t use_parity;
+ uint8_t pin[4];
+} em4x70_cmd_input_unlock_t;
+
+typedef struct _em4x70_cmd_input_auth_t {
+ uint8_t use_parity;
+ ID48LIB_NONCE rn;
+ ID48LIB_FRN frn;
+} em4x70_cmd_input_auth_t;
+typedef struct _em4x70_cmd_output_auth_t {
+ ID48LIB_GRN grn;
+} em4x70_cmd_output_auth_t;
+
+typedef struct _em4x70_cmd_input_writepin_t {
+ uint8_t use_parity;
+ uint8_t pin[4];
+} em4x70_cmd_input_writepin_t;
+
+typedef struct _em4x70_cmd_input_writekey_t {
+ uint8_t use_parity;
+ ID48LIB_KEY key;
+} em4x70_cmd_input_writekey_t;
+// There is no output data when writing a new key
+typedef struct _em4x70_cmd_input_recover_t {
+ ID48LIB_KEY key; // only the first 6 bytes (48 bits) are considered valid
+ ID48LIB_NONCE nonce;
+ ID48LIB_FRN frn;
+ ID48LIB_GRN grn;
+ bool parity; // if true, add parity bit to commands sent to tag
+ bool verify; // if true, tag must be present
+} em4x70_cmd_input_recover_t;
+// largest seen "in the wild" was 6
+#define MAXIMUM_ID48_RECOVERED_KEY_COUNT 10
+typedef struct _em4x70_cmd_output_recover_t {
+ uint8_t potential_key_count;
+ ID48LIB_KEY potential_keys[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
+} em4x70_cmd_output_recover_t;
+typedef struct _em4x70_cmd_input_verify_auth_t {
+ uint8_t use_parity;
+ ID48LIB_NONCE rn;
+ ID48LIB_FRN frn;
+ ID48LIB_GRN grn;
+} em4x70_cmd_input_verify_auth_t;
+
+
static int CmdHelp(const char *Cmd);
-static void fill_buffer_prng_bytes(void* buffer, size_t byte_count) {
+static void fill_buffer_prng_bytes(void *buffer, size_t byte_count) {
if (byte_count <= 0) return;
srand((unsigned) time(NULL));
for (size_t i = 0; i < byte_count; i++) {
- ((uint8_t*)buffer)[i] = (uint8_t)rand();
+ ((uint8_t *)buffer)[i] = (uint8_t)rand();
}
}
-
-
-static void print_info_result(const uint8_t *data) {
-
+static void print_info_result(const em4x70_tag_info_t *data) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------");
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(INFO, " %2d | %02X %02X | %s", 15, data->Raw[31], data->Raw[30], "UM2");
+ PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 14, data->Raw[29], data->Raw[28], "UM2");
+ PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 13, data->Raw[27], data->Raw[26], "UM2");
+ PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 12, data->Raw[25], data->Raw[24], "UM2");
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, " %2d | -- -- | %s", 11, "PIN write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 10, "PIN write only");
PrintAndLogEx(INFO, "------+----------+-----------------------------");
-
- // Print 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, " %2d | -- -- | %s", 9, "KEY write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 8, "KEY write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 7, "KEY write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 6, "KEY write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 5, "KEY write only");
+ PrintAndLogEx(INFO, " %2d | -- -- | %s", 4, "KEY write only");
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, " %2d | %02X %02X | %s", 3, data->Raw[ 7], data->Raw[ 6], "ID");
+ PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 2, data->Raw[ 5], data->Raw[ 4], "ID");
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, " %2d | %02X %02X | %s", 1, data->Raw[ 3], data->Raw[ 2], "UM1");
+ PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 0, data->Raw[ 1], data->Raw[ 0], "UM1");
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, "");
+ PrintAndLogEx(INFO, "Tag ID: %02X %02X %02X %02X", data->Raw[7], data->Raw[6], data->Raw[5], data->Raw[4]);
+ PrintAndLogEx(INFO, "Lockbit 0: %d", (data->Raw[3] & LOCKBIT_0) ? 1 : 0);
+ PrintAndLogEx(INFO, "Lockbit 1: %d", (data->Raw[3] & LOCKBIT_1) ? 1 : 0);
+ PrintAndLogEx(INFO, "Tag is %s.", (data->Raw[3] & LOCKBIT_0) ? _RED_("LOCKED") : _GREEN_("UNLOCKED"));
+ PrintAndLogEx(INFO, "");
+
+ PrintAndLogEx(NORMAL, "");
}
-// Note: arm source has a function with same name ... different signature.
-static int em4x70_info(void) {
+static int get_em4x70_info(const em4x70_cmd_input_info_t *opts, em4x70_tag_info_t *data_out) {
- em4x70_data_t edata = {
- .parity = false // TODO: try both? or default to true
- };
+ memset(data_out, 0, sizeof(em4x70_tag_info_t));
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t edata = { .parity = opts->use_parity };
clearCommandBuffer();
- SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(edata));
+ SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(em4x70_data_t));
PacketResponseNG resp;
if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) {
- PrintAndLogEx(WARNING, "(em4x70) Timeout while waiting for reply.");
+ return PM3_ETIMEOUT;
+ }
+ if (resp.status) {
+ memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+}
+static int writeblock_em4x70(const em4x70_cmd_input_writeblock_t *opts, em4x70_tag_info_t *data_out) {
+
+ memset(data_out, 0, sizeof(em4x70_tag_info_t));
+
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.address = opts->block;
+ etd.word = BYTES2UINT16(opts->value);
+ etd.parity = opts->use_parity;
+
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_WRITE, (uint8_t *)&etd, sizeof(etd));
+
+ PacketResponseNG resp;
+ if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITE, &resp, TIMEOUT)) {
return PM3_ETIMEOUT;
}
if (resp.status) {
- print_info_result(resp.data.asBytes);
+ memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
return PM3_SUCCESS;
}
-
return PM3_ESOFT;
}
+static int auth_em4x70(const em4x70_cmd_input_auth_t *opts, em4x70_cmd_output_auth_t *data_out) {
+ memset(data_out, 0, sizeof(ID48LIB_GRN));
-//quick test for EM4x70 tag
-bool detect_4x70_block(void) {
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.parity = opts->use_parity;
+ memcpy(&etd.rnd[0], &opts->rn.rn[0], 7);
+ memcpy(&etd.frnd[0], &opts->frn.frn[0], 4);
- return em4x70_info() == PM3_SUCCESS;
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_AUTH, (uint8_t *)&etd, sizeof(etd));
+
+ PacketResponseNG resp;
+ if (!WaitForResponseTimeout(CMD_LF_EM4X70_AUTH, &resp, TIMEOUT)) {
+ return PM3_ETIMEOUT;
+ }
+ if (resp.status) {
+ // Response is 20-bit from tag
+
+ // HACKHACK -- It appears the byte order differs from what is expected?
+ data_out->grn.grn[0] = resp.data.asBytes[2];
+ data_out->grn.grn[1] = resp.data.asBytes[1];
+ data_out->grn.grn[2] = resp.data.asBytes[0];
+ //memcpy(data_out, &resp.data.asBytes[0], sizeof(ID48LIB_GRN));
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+}
+static int writekey_em4x70(const em4x70_cmd_input_writekey_t *opts) {
+
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.parity = opts->use_parity;
+ memcpy(&etd.crypt_key[0], &opts->key.k[0], 12);
+
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_WRITEKEY, (uint8_t *)&etd, sizeof(etd));
+
+ PacketResponseNG resp;
+ if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITEKEY, &resp, TIMEOUT)) {
+ return PM3_ETIMEOUT;
+ }
+ if (resp.status) {
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+}
+static int brute_em4x70(const em4x70_cmd_input_brute_t *opts, em4x70_cmd_output_brute_t *data_out) {
+ memset(data_out, 0, sizeof(em4x70_cmd_output_brute_t));
+
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.parity = opts->use_parity;
+ etd.address = opts->block;
+ memcpy(&etd.rnd[0], &opts->rn.rn[0], 7);
+ memcpy(&etd.frnd[0], &opts->frn.frn[0], 4);
+
+ // TODO: FIX THIS MESS WITH BYTE ORDER CHANGING BACK AND FORTH!
+ // Just use byte arrays when sending to the firmware.
+ // Lowers the cognitive load AND makes it easier to understand.
+ // opts structure stored value in BIG ENDIAN
+ // Note that the FIRMWARE side will swap the byte order back to BIG ENDIAN.
+ // (yes, this is a bit of a mess, but it is what it is for now...)
+ uint16_t start_key_be = (opts->partial_key_start[0] << 8) | opts->partial_key_start[1];
+ etd.start_key = start_key_be;
+
+ clearCommandBuffer();
+ PacketResponseNG resp;
+ SendCommandNG(CMD_LF_EM4X70_BRUTE, (uint8_t *)&etd, sizeof(etd));
+
+ uint32_t timeout = 0;
+ for (;;) {
+
+ if (kbd_enter_pressed()) {
+ SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
+ return PM3_EOPABORTED;
+ }
+
+ if (WaitForResponseTimeout(CMD_LF_EM4X70_BRUTE, &resp, TIMEOUT)) {
+ if (resp.status) {
+ memcpy(data_out, resp.data.asBytes, sizeof(em4x70_cmd_output_brute_t));
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+ }
+
+ // NOTE: It takes about 11 seconds per 0x0100 authentication attempts.
+ // Thus, each block takes a maximum of 256 * 11 seconds == 46m56s.
+ // A timeout of 60 minutes corresponds to ~14 seconds per 0x0100 auths,
+ // which is ~25% margin. Plus, on average, it takes half that
+ // amount of time (for a random value in the key block).
+ if (timeout > ((60u * 60000u) / TIMEOUT)) {
+ PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
+ return PM3_ETIMEOUT;
+ }
+ timeout++;
+ }
+}
+static int unlock_em4x70(const em4x70_cmd_input_unlock_t *opts, em4x70_tag_info_t *data_out) {
+ memset(data_out, 0, sizeof(em4x70_tag_info_t));
+
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.parity = opts->use_parity;
+ etd.pin = BYTES2UINT32(opts->pin);
+
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_UNLOCK, (uint8_t *)&etd, sizeof(etd));
+
+ PacketResponseNG resp;
+ if (!WaitForResponseTimeout(CMD_LF_EM4X70_UNLOCK, &resp, TIMEOUT)) {
+ return PM3_ETIMEOUT;
+ }
+
+ if (resp.status) {
+ memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+
+}
+static int writepin_em4x70(const em4x70_cmd_input_writepin_t *opts, em4x70_tag_info_t *data_out) {
+ memset(data_out, 0, sizeof(em4x70_tag_info_t));
+
+ // TODO: change firmware to use per-cmd structures
+ em4x70_data_t etd = {0};
+ etd.parity = opts->use_parity;
+ etd.pin = BYTES2UINT32(opts->pin);
+
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_WRITEPIN, (uint8_t *)&etd, sizeof(etd));
+
+ PacketResponseNG resp;
+ if (!WaitForResponseTimeout(CMD_LF_EM4X70_WRITEPIN, &resp, TIMEOUT)) {
+ return PM3_ETIMEOUT;
+ }
+ if (resp.status) {
+ memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
+ return PM3_SUCCESS;
+ }
+ return PM3_ESOFT;
+}
+static int recover_em4x70(const em4x70_cmd_input_recover_t *opts, em4x70_cmd_output_recover_t *data_out) {
+ memset(data_out, 0, sizeof(em4x70_cmd_output_recover_t));
+
+ // The library is stateful. First must initialize its internal context.
+ id48lib_key_recovery_init(&opts->key, &opts->nonce, &opts->frn, &opts->grn);
+
+ // repeatedly call id48lib_key_recovery_next() to get the next potential key
+ ID48LIB_KEY q;
+ int result = PM3_SUCCESS;
+ while ((PM3_SUCCESS == result) && id48lib_key_recovery_next(&q)) {
+ if (data_out->potential_key_count >= MAXIMUM_ID48_RECOVERED_KEY_COUNT) {
+ result = PM3_EOVFLOW;
+ } else {
+ data_out->potential_keys[data_out->potential_key_count] = q;
+ ++data_out->potential_key_count;
+ }
+ }
+ if ((PM3_SUCCESS == result) && (data_out->potential_key_count == 0)) {
+ result = PM3_EFAILED;
+ }
+ return result;
+}
+static int verify_auth_em4x70(const em4x70_cmd_input_verify_auth_t *opts) {
+ em4x70_cmd_input_auth_t opts_auth = {
+ .use_parity = opts->use_parity,
+ .rn = opts->rn,
+ .frn = opts->frn,
+ };
+ em4x70_cmd_output_auth_t tag_grn;
+ int result = auth_em4x70(&opts_auth, &tag_grn);
+ if (PM3_SUCCESS == result) {
+ if (memcmp(&opts->grn, &tag_grn, sizeof(ID48LIB_GRN)) != 0) {
+ result = PM3_EWRONGANSWER;
+ }
+ }
+ return result;
}
-// TODO: split the below functions, so can use them as building blocks for more complex interactions
-// without generating fake `const char *Cmd` strings. First targets:
-// Auth
-// Write
-// WriteKey
-// Together, they will allow writekey to verify the key was written correctly.
+// used by `lf search` and `search`, this is a quick test for EM4x70 tag
+// In alignment with other tags implementations, this also dumps basic information
+// about the tag, if one is found.
+// Use helper function `get_em4x70_info()` if wanting to limit / avoid output.
+bool detect_4x70_block(void) {
+ em4x70_tag_info_t info;
+ em4x70_cmd_input_info_t opts = { 0 };
+ int result = get_em4x70_info(&opts, &info);
+ if (result == PM3_ETIMEOUT) { // consider removing this output?
+ PrintAndLogEx(WARNING, "(em4x70) Timeout while waiting for reply.");
+ }
+ return result == PM3_SUCCESS;
+}
int CmdEM4x70Info(const char *Cmd) {
- // envoke reading of a EM4x70 tag which has to be on the antenna because
+ // invoke reading of a EM4x70 tag which has to be on the antenna because
// decoding is done by the device (not on client side)
-
- em4x70_data_t etd = {0};
-
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 info",
@@ -152,32 +464,27 @@ int CmdEM4x70Info(const char *Cmd) {
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
- etd.parity = arg_get_lit(ctx, 0);
+ em4x70_cmd_input_info_t opts = {
+ .use_parity = arg_get_lit(ctx, 0),
+ };
CLIParserFree(ctx);
- clearCommandBuffer();
- SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&etd, sizeof(etd));
-
- PacketResponseNG resp;
- if (!WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT)) {
- PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
- return PM3_ETIMEOUT;
+ // Client command line parsing and validation complete ... now use the helper function
+ em4x70_tag_info_t info;
+ int result = get_em4x70_info(&opts, &info);
+ if (result == PM3_ETIMEOUT) {
+ PrintAndLogEx(WARNING, "(em4x70) Timeout while waiting for reply.");
+ } else if (result == PM3_SUCCESS) {
+ print_info_result(&info);
+ } else {
+ PrintAndLogEx(FAILED, "Reading " _RED_("Failed"));
}
-
- if (resp.status) {
- print_info_result(resp.data.asBytes);
- return PM3_SUCCESS;
- }
-
- PrintAndLogEx(FAILED, "Reading " _RED_("Failed"));
- return PM3_ESOFT;
+ return result;
}
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",
@@ -196,55 +503,43 @@ int CmdEM4x70Write(const char *Cmd) {
CLIExecWithReturn(ctx, Cmd, argtable, true);
- etd.parity = arg_get_lit(ctx, 1);
-
- int addr = arg_get_int_def(ctx, 2, 1);
-
- int word_len = 0;
- uint8_t word[2] = {0x0};
- CLIGetHexWithReturn(ctx, 3, word, &word_len);
+ em4x70_cmd_input_writeblock_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .block = arg_get_int_def(ctx, 2, 1),
+ .value = {0}, // hex value macro exits function, so cannot be initialized here
+ };
+ int value_len = 0;
+ CLIGetHexWithReturn(ctx, 3, opts.value, &value_len);
CLIParserFree(ctx);
- if (addr < 0 || addr >= EM4X70_NUM_BLOCKS) {
- PrintAndLogEx(FAILED, "block has to be within range [0, 15] got: %d", addr);
+ if (opts.block >= EM4X70_NUM_BLOCKS) {
+ PrintAndLogEx(FAILED, "block has to be within range [0, 15] got: %d", opts.block);
+ return PM3_EINVARG;
+ }
+ if (value_len != 2) {
+ PrintAndLogEx(FAILED, "word/data length must be 2 bytes. got: %d", value_len);
return PM3_EINVARG;
}
- if (word_len != 2) {
- PrintAndLogEx(FAILED, "word/data length must be 2 bytes. got: %d", word_len);
- return PM3_EINVARG;
+ // Client command line parsing and validation complete ... now use the helper function
+ em4x70_tag_info_t info;
+ int result = writeblock_em4x70(&opts, &info);
+ if (result == PM3_ETIMEOUT) {
+ PrintAndLogEx(WARNING, "(em4x70) Timeout while waiting for reply.");
+ } else if (result == PM3_SUCCESS) {
+ print_info_result(&info);
+ } else {
+ PrintAndLogEx(FAILED, "Writing " _RED_("Failed"));
}
-
- 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;
+ return result;
}
int CmdEM4x70Brute(const char *Cmd) {
// From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
// Partial Key-Update Attack (optimized version)
- em4x70_data_t etd = {0};
-
CLIParserContext *ctx;
-
CLIParserInit(&ctx, "lf em 4x70 brute",
"Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70\n"
"This attack does NOT write anything to the tag.\n"
@@ -252,7 +547,6 @@ int CmdEM4x70Brute(const char *Cmd) {
"After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'\n",
"lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 --> bruteforcing key bits k95...k80\n"
);
-
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"),
@@ -264,33 +558,41 @@ int CmdEM4x70Brute(const char *Cmd) {
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
- etd.parity = arg_get_lit(ctx, 1);
+ em4x70_cmd_input_brute_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .block = arg_get_int_def(ctx, 2, 0),
+ .rn = {{0}}, // hex value macro exits function, so cannot be initialized here
+ .frn = {{0}}, // hex value macro exits function, so cannot be initialized here
+ .partial_key_start = {0}, // hex value macro exits function, so cannot be initialized here
+ };
- int addr = arg_get_int_def(ctx, 2, 0);
- if (addr < 7 || addr > 9) {
- PrintAndLogEx(FAILED, "block has to be within range [7, 9] got: %d", addr);
+ if (opts.block < 7 || opts.block > 9) {
+ PrintAndLogEx(FAILED, "block has to be within range [7, 9] got: %d", opts.block);
CLIParserFree(ctx);
return PM3_EINVARG;
}
- etd.address = (uint8_t) addr;
int rnd_len = 7;
- CLIGetHexWithReturn(ctx, 3, etd.rnd, &rnd_len);
+ CLIGetHexWithReturn(ctx, 3, opts.rn.rn, &rnd_len);
int frnd_len = 4;
- CLIGetHexWithReturn(ctx, 4, etd.frnd, &frnd_len);
+ CLIGetHexWithReturn(ctx, 4, opts.frn.frn, &frnd_len);
+ // would prefer to use above CLIGetHexWithReturn(), but it does not
+ // appear to support optional arguments.
uint32_t start_key = 0;
- int res = arg_get_u32_hexstr_def_nlen(ctx, 5, 0, &start_key, 2, true);
+ int res = arg_get_u32_hexstr_def_nlen(ctx, 5, 0, &start_key, 2, true); // this stores in NATIVE ENDIAN
if (res == 2) {
PrintAndLogEx(WARNING, "start key parameter must be in range [0, FFFF]");
CLIParserFree(ctx);
return PM3_EINVARG;
}
- etd.start_key = start_key;
-
CLIParserFree(ctx);
+ // opts structure takes value in BIG ENDIAN form
+ opts.partial_key_start[0] = (uint8_t)((start_key >> 8) & 0xFF);
+ opts.partial_key_start[1] = (uint8_t)((start_key >> 0) & 0xFF);
+
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
return PM3_EINVARG;
@@ -301,48 +603,26 @@ int CmdEM4x70Brute(const char *Cmd) {
return PM3_EINVARG;
}
+ // Client command line parsing and validation complete ... now use the helper function
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to exit");
- clearCommandBuffer();
- PacketResponseNG resp;
- SendCommandNG(CMD_LF_EM4X70_BRUTE, (uint8_t *)&etd, sizeof(etd));
-
- uint32_t timeout = 0;
- for (;;) {
-
- if (kbd_enter_pressed()) {
- SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
- PrintAndLogEx(DEBUG, "User aborted");
- break;
- }
-
- if (WaitForResponseTimeout(CMD_LF_EM4X70_BRUTE, &resp, TIMEOUT)) {
- if (resp.status) {
- // Response is 16-bit partial key
- PrintAndLogEx(INFO, "Partial Key Response: %02X %02X", resp.data.asBytes[0], resp.data.asBytes[1]);
- return PM3_SUCCESS;
- }
- break;
- }
-
- // should be done in about 60 minutes.
- if (timeout > ((60 * 60000) / TIMEOUT)) {
- PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
- break;
- }
- timeout++;
+ em4x70_cmd_output_brute_t data;
+ int result = brute_em4x70(&opts, &data);
+ if (result == PM3_EOPABORTED) {
+ PrintAndLogEx(DEBUG, "User aborted");
+ } else if (result == PM3_ETIMEOUT) {
+ PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
+ } else if (result == PM3_SUCCESS) {
+ PrintAndLogEx(INFO, "Partial Key Response: %02X %02X", data.partial_key[0], data.partial_key[1]);
+ } else {
+ PrintAndLogEx(FAILED, "Bruteforce of partial key " _RED_("failed"));
}
-
- PrintAndLogEx(FAILED, "Bruteforce of partial key " _RED_("failed"));
- return PM3_ESOFT;
+ return result;
}
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"
@@ -351,7 +631,6 @@ int CmdEM4x70Unlock(const char *Cmd) {
"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"),
@@ -361,13 +640,12 @@ int CmdEM4x70Unlock(const char *Cmd) {
CLIExecWithReturn(ctx, Cmd, argtable, true);
- etd.parity = arg_get_lit(ctx, 1);
-
+ em4x70_cmd_input_unlock_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .pin = {0}, // hex value macro exits function, so cannot be initialized here
+ };
int pin_len = 0;
- uint8_t pin[4] = {0x0};
-
- CLIGetHexWithReturn(ctx, 2, pin, &pin_len);
-
+ CLIGetHexWithReturn(ctx, 2, opts.pin, &pin_len);
CLIParserFree(ctx);
if (pin_len != 4) {
@@ -375,24 +653,17 @@ int CmdEM4x70Unlock(const char *Cmd) {
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)) {
+ // Client command line parsing and validation complete ... now use the helper function
+ em4x70_tag_info_t info;
+ int result = unlock_em4x70(&opts, &info);
+ if (result == PM3_ETIMEOUT) {
PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
- return PM3_ETIMEOUT;
+ } else if (result == PM3_SUCCESS) {
+ print_info_result(&info);
+ } else {
+ PrintAndLogEx(FAILED, "Unlocking tag " _RED_("Failed"));
}
-
- if (resp.status) {
- print_info_result(resp.data.asBytes);
- return PM3_SUCCESS;
- }
-
- PrintAndLogEx(FAILED, "Unlocking tag " _RED_("failed"));
- return PM3_ESOFT;
+ return result;
}
int CmdEM4x70Auth(const char *Cmd) {
@@ -400,8 +671,6 @@ 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",
@@ -422,73 +691,61 @@ int CmdEM4x70Auth(const char *Cmd) {
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);
+ em4x70_cmd_input_auth_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .rn = {{0}}, // hex value macro exits function, so cannot be initialized here
+ .frn = {{0}}, // hex value macro exits function, so cannot be initialized here
+ };
+ int rn_len = 7;
+ CLIGetHexWithReturn(ctx, 2, opts.rn.rn, &rn_len);
+ int frn_len = 4;
+ CLIGetHexWithReturn(ctx, 3, opts.frn.frn, &frn_len);
CLIParserFree(ctx);
-
- if (rnd_len != 7) {
- PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
+ if (rn_len != 7) {
+ PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rn_len);
+ return PM3_EINVARG;
+ }
+ if (frn_len != 4) {
+ PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frn_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)) {
+ // Client command line parsing and validation complete ... now use the helper function
+ em4x70_cmd_output_auth_t data;
+ int result = auth_em4x70(&opts, &data);
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(INFO, "Tag Auth Response: %02X %02X %02X", data.grn.grn[0], data.grn.grn[1], data.grn.grn[2]);
+ } else if (PM3_ETIMEOUT == result) {
PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
- return PM3_ETIMEOUT;
+ } else {
+ PrintAndLogEx(FAILED, "TAG Authentication " _RED_("Failed"));
}
-
- 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;
+ return result;
}
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);
+ em4x70_cmd_input_writepin_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .pin = {0}, // hex value macro exits function, so cannot be initialized here
+ };
int pin_len = 0;
- uint8_t pin[4] = {0x0};
-
- CLIGetHexWithReturn(ctx, 2, pin, &pin_len);
-
+ CLIGetHexWithReturn(ctx, 2, opts.pin, &pin_len);
CLIParserFree(ctx);
if (pin_len != 4) {
@@ -496,34 +753,22 @@ int CmdEM4x70WritePIN(const char *Cmd) {
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)) {
+ // Client command line parsing and validation complete ... now use the helper function
+ em4x70_tag_info_t info;
+ int result = writepin_em4x70(&opts, &info);
+ if (result == PM3_ETIMEOUT) {
PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
- return PM3_ETIMEOUT;
- }
-
- if (resp.status) {
- print_info_result(resp.data.asBytes);
+ } else if (result == PM3_SUCCESS) {
+ print_info_result(&info);
PrintAndLogEx(INFO, "Writing new PIN: " _GREEN_("ok"));
- return PM3_SUCCESS;
+ } else {
+ PrintAndLogEx(FAILED, "Writing new PIN: " _RED_("failed"));
}
-
- PrintAndLogEx(FAILED, "Writing new PIN: " _RED_("failed"));
- return PM3_ESOFT;
+ return result;
}
int CmdEM4x70WriteKey(const char *Cmd) {
-
- // Write new 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 (pm3 test key)\n"
@@ -539,63 +784,92 @@ int CmdEM4x70WriteKey(const char *Cmd) {
CLIExecWithReturn(ctx, Cmd, argtable, true);
- etd.parity = arg_get_lit(ctx, 1);
-
+ em4x70_cmd_input_writekey_t opts = {
+ .use_parity = arg_get_lit(ctx, 1),
+ .key = {{0}}, // hex value macro exits function, so cannot be initialized here
+ };
int key_len = 12;
- CLIGetHexWithReturn(ctx, 2, etd.crypt_key, &key_len);
-
+ CLIGetHexWithReturn(ctx, 2, opts.key.k, &key_len);
CLIParserFree(ctx);
-
if (key_len != 12) {
PrintAndLogEx(FAILED, "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)) {
+ // Client command line parsing and validation complete ... now use the helper function
+ int result = writekey_em4x70(&opts);
+ if (PM3_ETIMEOUT == result) {
PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
return PM3_ETIMEOUT;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Writing new key: " _RED_("failed"));
+ return result;
}
- if (resp.status) {
- PrintAndLogEx(INFO, "Writing new key: " _GREEN_("ok"));
+ PrintAndLogEx(INFO, "Writing new key: " _GREEN_("ok"));
- // TODO: use prng to generate a new nonce, calculate frn/grn, and authenticate with tag
+ // Now verify authentication using the new key, to ensure it was correctly written
+ em4x70_cmd_input_verify_auth_t opts_v = {
+ .use_parity = opts.use_parity,
+ //.rn = opts_auth.rn,
+ //.frn = opts_auth.frn,
+ //.grn = {{0}},
+ };
+ fill_buffer_prng_bytes(&opts_v.rn, sizeof(ID48LIB_NONCE));
+ id48lib_generator(&opts.key, &opts_v.rn, &opts_v.frn, &opts_v.grn);
- return PM3_SUCCESS;
+ // dump the auth command to the screen, to enable the user to manually check validity
+ PrintAndLogEx(INFO,
+ "Verifying auth for new key: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+ " --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
+ " --> %02X%02X%02X",
+ opts.key.k[ 0], opts.key.k[ 1], opts.key.k[ 2], opts.key.k[ 3], opts.key.k[ 4], opts.key.k[ 5],
+ opts.key.k[ 6], opts.key.k[ 7], opts.key.k[ 8], opts.key.k[ 9], opts.key.k[10], opts.key.k[11],
+ opts_v.rn.rn[0],
+ opts_v.rn.rn[1],
+ opts_v.rn.rn[2],
+ opts_v.rn.rn[3],
+ opts_v.rn.rn[4],
+ opts_v.rn.rn[5],
+ opts_v.rn.rn[6],
+ opts_v.frn.frn[0],
+ opts_v.frn.frn[1],
+ opts_v.frn.frn[2],
+ opts_v.frn.frn[3],
+ opts_v.grn.grn[0],
+ opts_v.grn.grn[1],
+ opts_v.grn.grn[2]
+ );
+ result = verify_auth_em4x70(&opts_v);
+ if (PM3_ETIMEOUT == result) {
+ PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Authenticating with new key: " _RED_("failed"));
+ return result;
+ } else {
+ PrintAndLogEx(INFO, "Authenticating with new key: " _GREEN_("ok"));
}
-
- PrintAndLogEx(FAILED, "Writing new key: " _RED_("failed"));
- return PM3_ESOFT;
+ return result;
}
-// largest seen "in the wild" was 6
-#define MAXIMUM_ID48_RECOVERED_KEY_COUNT 10
typedef struct _em4x70_recovery_data_t {
- ID48LIB_KEY key;
- ID48LIB_NONCE nonce;
- ID48LIB_FRN frn;
- ID48LIB_GRN grn;
- bool verify; // if true, tag must be present
- bool parity; // if true, add parity bit to commands sent to tag
- uint8_t keys_found_count;
+ em4x70_cmd_input_recover_t opts;
+ em4x70_cmd_output_recover_t data;
+
uint8_t keys_validated_count;
- ID48LIB_KEY potential_keys[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_NONCE alt_nonce;
ID48LIB_FRN alt_frn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_GRN alt_grn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
bool potential_keys_validated[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
} em4x70_recovery_data_t;
-static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_results) {
+static int CmdEM4x70Recover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_t *out_results) {
memset(out_results, 0, sizeof(em4x70_recovery_data_t));
int result = PM3_SUCCESS;
-
+
CLIParserContext *ctx;
CLIParserInit(
&ctx,
@@ -614,7 +888,7 @@ static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_r
,
"lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
- );
+ );
void *argtable[] = {
arg_param_begin,
@@ -652,7 +926,7 @@ static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_r
if (CLIParamHexToBuf(arg_get_str(ctx, 5), &(out_results->grn.grn[0]), 3, &grn_len)) {
result = PM3_ESOFT;
}
- //out_results->verify = arg_get_lit(ctx, 6);
+ //out_results->verify = arg_get_lit(ctx, 6);
}
// if all OK so far, do additional parameter validation
if (PM3_SUCCESS == result) {
@@ -676,7 +950,6 @@ static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_r
}
if (PM3_SUCCESS == result) {
- fill_buffer_prng_bytes(&out_results->alt_nonce, sizeof(ID48LIB_NONCE));
}
// single exit point
@@ -688,73 +961,63 @@ int CmdEM4x70Recover(const char *Cmd) {
// Partial Key-Update Attack -- final 48 bits (after optimized version gets k95..k48)
em4x70_recovery_data_t recover_ctx = {0};
int result = PM3_SUCCESS;
-
- result = ValidateArgsForRecover(Cmd, &recover_ctx);
+
+ result = CmdEM4x70Recover_ParseArgs(Cmd, &recover_ctx.opts);
// recover the potential keys -- no more than a few seconds
if (PM3_SUCCESS == result) {
- // The library is stateful. First must initialize its internal context.
- id48lib_key_recovery_init(&recover_ctx.key, &recover_ctx.nonce, &recover_ctx.frn, &recover_ctx.grn);
-
- // repeatedly call id48lib_key_recovery_next() to get the next potential key
- ID48LIB_KEY q;
- while ((PM3_SUCCESS == result) && id48lib_key_recovery_next(&q)) {
- if (recover_ctx.keys_found_count >= MAXIMUM_ID48_RECOVERED_KEY_COUNT) {
- PrintAndLogEx(ERR, "Found more than %d potential keys. This is unexpected and likely a code failure.", MAXIMUM_ID48_RECOVERED_KEY_COUNT);
- result = PM3_EFAILED;
- } else {
- recover_ctx.potential_keys[recover_ctx.keys_found_count] = q;
- ++recover_ctx.keys_found_count;
- }
- }
- if (recover_ctx.keys_found_count == 0) {
+ result = recover_em4x70(&recover_ctx.opts, &recover_ctx.data);
+ if (PM3_EOVFLOW == result) {
+ PrintAndLogEx(ERR, "Found more than %d potential keys. This is unexpected and likely a code failure.", MAXIMUM_ID48_RECOVERED_KEY_COUNT);
+ } else if (PM3_SUCCESS != result) {
PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
- result = PM3_EFAILED;
}
}
- // generate alternate authentication for each potential key -- sub-second execution, no error paths
+ // generate alternate authentication for each potential key -- no error paths, sub-second execution
if (PM3_SUCCESS == result) {
- for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
+ fill_buffer_prng_bytes(&recover_ctx.alt_nonce, sizeof(ID48LIB_NONCE));
+ for (uint8_t i = 0; i < recover_ctx.data.potential_key_count; ++i) {
// generate the alternate frn/grn for the alternate nonce
- id48lib_generator(&recover_ctx.potential_keys[i], &recover_ctx.alt_nonce, &recover_ctx.alt_frn[i], &recover_ctx.alt_grn[i]);
+ id48lib_generator(&recover_ctx.data.potential_keys[i], &recover_ctx.alt_nonce, &recover_ctx.alt_frn[i], &recover_ctx.alt_grn[i]);
}
}
// display alternate authentication for each potential key -- no error paths
if (PM3_SUCCESS == result) {
- PrintAndLogEx(INFO, "Recovered %d potential keys:", recover_ctx.keys_found_count);
- for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
+ PrintAndLogEx(INFO, "Recovered %d potential keys:", recover_ctx.data.potential_key_count);
+ for (uint8_t i = 0; i < recover_ctx.data.potential_key_count; ++i) {
// generate an alternative authentication based on the potential key
// and the alternate nonce.
- ID48LIB_KEY q = recover_ctx.potential_keys[i];
+ ID48LIB_KEY q = recover_ctx.data.potential_keys[i];
ID48LIB_FRN alt_frn = recover_ctx.alt_frn[i];
ID48LIB_GRN alt_grn = recover_ctx.alt_grn[i];
// dump the results to screen, to enable the user to manually check validity
PrintAndLogEx(INFO,
- "Potential Key #%d: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
- " --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
- " --> %02X%02X%02X",
- i,
- q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
- q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11],
- recover_ctx.alt_nonce.rn[0],
- recover_ctx.alt_nonce.rn[1],
- recover_ctx.alt_nonce.rn[2],
- recover_ctx.alt_nonce.rn[3],
- recover_ctx.alt_nonce.rn[4],
- recover_ctx.alt_nonce.rn[5],
- recover_ctx.alt_nonce.rn[6],
- alt_frn.frn[0],
- alt_frn.frn[1],
- alt_frn.frn[2],
- alt_frn.frn[3],
- alt_grn.grn[0],
- alt_grn.grn[1],
- alt_grn.grn[2]
- );
+ "Potential Key #%d: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+ " --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
+ " --> %02X%02X%02X",
+ i,
+ q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
+ q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11],
+ recover_ctx.alt_nonce.rn[0],
+ recover_ctx.alt_nonce.rn[1],
+ recover_ctx.alt_nonce.rn[2],
+ recover_ctx.alt_nonce.rn[3],
+ recover_ctx.alt_nonce.rn[4],
+ recover_ctx.alt_nonce.rn[5],
+ recover_ctx.alt_nonce.rn[6],
+ alt_frn.frn[0],
+ alt_frn.frn[1],
+ alt_frn.frn[2],
+ alt_frn.frn[3],
+ alt_grn.grn[0],
+ alt_grn.grn[1],
+ alt_grn.grn[2]
+ );
}
printf("\n");
}
- if (PM3_SUCCESS == result && recover_ctx.verify) {
+ // which of those keys actually validates?
+ if (PM3_SUCCESS == result && recover_ctx.opts.verify) {
// TODO: automatic verification against a present tag.
// Updates ctx.potential_keys_validated[10] and ctx.keys_validated_count
PrintAndLogEx(WARNING, "Automatic verification against tag is not yet implemented.");
@@ -774,21 +1037,365 @@ int CmdEM4x70Recover(const char *Cmd) {
// 4. if ctx.keys_validated_count >= 2, print "Multiple keys recovered. Run command again (will use different alt nonce)?"
// 5. if ctx.keys_validated_count == 1, print "Found key: " ...
}
-
-
return result;
}
+static int CmdEM4x70AutoRecover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_t *out_results) {
+ memset(out_results, 0, sizeof(em4x70_cmd_input_recover_t));
+
+ int result = PM3_SUCCESS;
+ // The following key is found quickly, and has multiple potential keys.
+ // Useful for quicker testing, as this function could take over 2 hours.
+ // lf em 4x70 writekey -k 001200340055BAADCAFEF00D
+ // lf em 4x70 autorecover --rnd 1782779E7E3BC8 --frn 00357080 --grn F3C480
+ CLIParserContext *ctx;
+ CLIParserInit(
+ &ctx,
+ "lf em 4x70 autorecover",
+ "This command will perform automatic recovery of the key from a writable tag.\n"
+ "All steps are possible to do manually. The corresponding sequence, if done\n"
+ "manually, is as follows:\n"
+ "1. Verify passed parameters authenticate with the tag (safety check)\n"
+ " " _YELLOW_("lf em 4x70 auth --rnd --frn ") "\n"
+ "2. Brute force the key bits in block 9\n"
+ " " _YELLOW_("lf em 4x70 write -b 9 -d 0000") "\n"
+ " " _YELLOW_("lf em 4x70 recover -b 9 --rnd --frn ") "\n"
+ " " _YELLOW_("lf em 4x70 write -b 9 -d ") "\n"
+ "3. Brute force the key bits in block 8\n"
+ " " _YELLOW_("lf em 4x70 write -b 8 -d 0000") "\n"
+ " " _YELLOW_("lf em 4x70 recover -b 8 --rnd --frn ") "\n"
+ " " _YELLOW_("lf em 4x70 write -b 8 -d ") "\n"
+ "4. Brute force the key bits in block 7\n"
+ " " _YELLOW_("lf em 4x70 write -b 7 -d 0000)") "\n"
+ " " _YELLOW_("lf em 4x70 recover -b 7 --rnd --frn ") "\n"
+ " " _YELLOW_("lf em 4x70 write -b 7 -d ") "\n"
+ "5. Recover potential values of the lower 48 bits of the key\n"
+ " " _YELLOW_("lf em 4x70 recover --key --rnd --frn ") "\n"
+ "6. Verify which potential key is actually on the tag (using a different rnd/frn combination)\n"
+ " " _YELLOW_("lf em 4x70 auth --rnd --frn ") "\n"
+ "7. Print the validated key\n"
+ "\n"
+ "This command simply requires the rnd/frn/grn from a single known-good authentication.\n"
+ "\n"
+ ""
+ // "\n"
+ // "If the `--verify` option is provided, the tag must be present. The rnd/frn parameters will\n"
+ // "be used to authenticate against the tag, and then any potential keys will be automatically\n"
+ // "be checked for correctness against the tag, reducing manual steps.\n"
+ ,
+ "lf em 4x70 autorecover --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
+ "lf em 4x70 autorecover --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
+ );
+
+ void *argtable[] = {
+ arg_param_begin,
+ arg_lit0(NULL, "par", "Add parity bit when sending commands"),
+ arg_str1(NULL, "rnd", "", "Random 56-bit from known-good authentication"),
+ arg_str1(NULL, "frn", "", "F(RN) 28-bit as 4 hex bytes from known-good authentication"),
+ arg_str1(NULL, "grn", "", "G(RN) 20-bit as 3 hex bytes from known-good authentication"),
+ //arg_lit0(NULL, "verify", "automatically use tag for validation"),
+ arg_param_end
+ };
+
+ CLIExecWithReturn(ctx, Cmd, argtable, true);
+
+ int rnd_len = 0; // must be 7 bytes hex data
+ int frn_len = 0; // must be 4 bytes hex data
+ int grn_len = 0; // must be 3 bytes hex data
+ out_results->parity = arg_get_lit(ctx, 1);
+ CLIGetHexWithReturn(ctx, 2, out_results->nonce.rn, &rnd_len);
+ CLIGetHexWithReturn(ctx, 3, out_results->frn.frn, &frn_len);
+ CLIGetHexWithReturn(ctx, 4, out_results->grn.grn, &grn_len);
+ CLIParserFree(ctx);
+
+ if (rnd_len != 7) {
+ PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
+ result = PM3_EINVARG;
+ }
+ if (frn_len != 4) {
+ PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frn_len);
+ result = PM3_EINVARG;
+ }
+ if (grn_len != 3) {
+ PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes instead of %d", grn_len);
+ result = PM3_EINVARG;
+ }
+
+ return result;
+}
+static int CmdEM4x70AutoRecover(const char *Cmd) {
+ em4x70_cmd_input_recover_t opts = {0};
+ em4x70_cmd_output_recover_t data = {0};
+ em4x70_tag_info_t tag_info = {0};
+ int result = CmdEM4x70AutoRecover_ParseArgs(Cmd, &opts);
+ // 0. Parse the command line
+ if (PM3_SUCCESS != result) {
+ return result;
+ }
+
+ // The parameters are valid. Per Iceman's direct request, the code has been updated
+ // to immediately exit on errors. Unfortunately, this requirement limits the clarity
+ // of summarizing the failure (and providing options for recovery in case of failures)
+ // at a single point at the end of the function. It will also undoubtedly reduce
+ // code coverage numbers, when those are tracked.
+ //
+ // As to clarity, if failures occurred in steps 2-4, it was expected that the cleanup
+ // code would, in a single location, verify if the original authentication worked.
+ // If so, then the tag was left in a good state (even if an error occurred).
+ // If not, then at least it would be possible for the user to restart manually, and
+ // to be given clear instructions on how to do that to return the tag to its original
+ // state.
+ //
+ // TODO: Wrap this entire function in another function, whose sole purpose is to
+ // perform that additional cleanup? Not a great solution. Pity, as the
+ // cleanup code was much more helpful than the below print statements.
+ int last_successful_step = 0;
+ char rnd_string[14 + 1] = {0};
+ char frn_string[ 8 + 1] = {0};
+ char grn_string[ 6 + 1] = {0};
+ // These strings will be re-used often, are safe to pre-allocate, and make later PrintAndLogEx() calls cleaner.
+ snprintf(rnd_string, 15, "%02X%02X%02X%02X%02X%02X%02X", opts.nonce.rn[0], opts.nonce.rn[1], opts.nonce.rn[2], opts.nonce.rn[3], opts.nonce.rn[4], opts.nonce.rn[5], opts.nonce.rn[6]);
+ snprintf(frn_string, 9, "%02X%02X%02X%02X", opts.frn.frn[0], opts.frn.frn[1], opts.frn.frn[2], opts.frn.frn[3]);
+ snprintf(grn_string, 7, "%02X%02X%02X", opts.grn.grn[0], opts.grn.grn[1], opts.grn.grn[2]);
+
+ // 1. Verify passed parameters authenticate with the tag (safety check)
+ // lf em 4x70 auth --rnd --frn
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(INFO, "Step 1. Verifying passed parameters authenticate with the tag (safety check)");
+ PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 auth --rnd %s --frn %s"), rnd_string, frn_string);
+ em4x70_cmd_input_auth_t opts_auth = {
+ .use_parity = opts.parity,
+ .rn = opts.nonce,
+ .frn = opts.frn,
+ };
+ em4x70_cmd_output_auth_t tag_grn;
+ result = auth_em4x70(&opts_auth, &tag_grn);
+ if (PM3_ETIMEOUT == result) {
+ PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Authenticating with provided values: " _RED_("failed"));
+ return result;
+ } else if (memcmp(&opts.grn, &tag_grn, sizeof(ID48LIB_GRN)) != 0) {
+ PrintAndLogEx(FAILED, "Authenticating with new key returned %02x %02x %02x, expected %s (maybe 5 lsb of key wrong?): " _RED_("failed"),
+ tag_grn.grn.grn[0], tag_grn.grn.grn[1], tag_grn.grn.grn[2],
+ grn_string
+ );
+ result = PM3_EWRONGANSWER;
+ return result;
+ }
+ last_successful_step = 1;
+ }
+
+ // 2/3/4. Brute force the key bits in block 7,8,9
+ // lf em 4x70 write -b N -d 0000
+ // lf em 4x70 brute -b N --rnd --frn
+ // lf em 4x70 write -b N -d
+ for (uint8_t block = 9; (PM3_SUCCESS == result) && (block > 6); --block) {
+ uint8_t step =
+ block == 9 ? 2 :
+ block == 8 ? 3 :
+ block == 7 ? 4 :
+ 197;
+ em4x70_cmd_output_brute_t brute = {0};
+
+ // lf em 4x70 write -b N -d 0000
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(INFO, "Step %d. Brute force the key bits in block %d", step, block);
+ PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 write -b %d -d 0000"), block);
+ em4x70_cmd_input_writeblock_t opt_write_zeros = {
+ .use_parity = opts.parity,
+ .block = block,
+ .value = {0x00, 0x00},
+ };
+ result = writeblock_em4x70(&opt_write_zeros, &tag_info);
+ if (PM3_ETIMEOUT == result) {
+ PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
+ PrintAndLogEx(HINT, "Block %d data may have been overwritten. Manually restart at step %d.", block, step);
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Writing block %d: " _RED_("failed") ".", block);
+ PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d.", block, step);
+ return result;
+ }
+ }
+ // lf em 4x70 brute -b N --rnd --frn
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 brute -b %d --rnd %s --frn %s"), block, rnd_string, frn_string);
+ em4x70_cmd_input_brute_t opts_brute = {
+ .use_parity = opts.parity,
+ .block = block,
+ .rn = opts.nonce,
+ .frn = opts.frn,
+ .partial_key_start = {0},
+ };
+
+ result = brute_em4x70(&opts_brute, &brute);
+ if (PM3_ETIMEOUT == result) {
+ PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
+ PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d.", block, step);
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Writing block %d: " _RED_("failed") ".", block);
+ PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d.", block, step);
+ return result;
+ } else {
+ PrintAndLogEx(INFO, " Found: Partial key in block %d is " _GREEN_("%02X%02X"), block, brute.partial_key[0], brute.partial_key[1]);
+ // Save the partial key...
+ if (block == 9) {
+ opts.key.k[0] = brute.partial_key[0];
+ opts.key.k[1] = brute.partial_key[1];
+ } else if (block == 8) {
+ opts.key.k[2] = brute.partial_key[0];
+ opts.key.k[3] = brute.partial_key[1];
+ } else if (block == 7) {
+ opts.key.k[4] = brute.partial_key[0];
+ opts.key.k[5] = brute.partial_key[1];
+ }
+ }
+ }
+ // lf em 4x70 write -b N -d
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 write -b %d -d %02X%02X"), block, brute.partial_key[0], brute.partial_key[1]);
+ em4x70_cmd_input_writeblock_t opt_write_zeros = {
+ .use_parity = opts.parity,
+ .block = block,
+ .value = {brute.partial_key[0], brute.partial_key[1]},
+ };
+ result = writeblock_em4x70(&opt_write_zeros, &tag_info);
+ if (PM3_ETIMEOUT == result) {
+ PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
+ PrintAndLogEx(HINT, "Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten.", block, brute.partial_key[0], brute.partial_key[1]);
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(FAILED, "Writing block %d: " _RED_("failed") ".", block);
+ PrintAndLogEx(HINT, "Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten.", block, brute.partial_key[0], brute.partial_key[1]);
+ return result;
+ }
+ }
+ if (PM3_SUCCESS == result) {
+ last_successful_step = step;
+ }
+ }
+ // The good news is that, if the above succeeded, then from this point forward, the tag remains in a known-good state.
+
+ char key_string[24 + 1] = {0}; // holds partial key initially, full key later
+ snprintf(key_string, 25, "%02X%02X%02X%02X%02X%02X", opts.key.k[0], opts.key.k[1], opts.key.k[2], opts.key.k[3], opts.key.k[4], opts.key.k[5]);
+
+ // 5. Recover potential values of the lower 48 bits of the key
+ // lf em 4x70 recover --key --rnd --frn
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(INFO, "Step 5. Recover potential values of the lower 48 bits of the key");
+ PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 recover --key %s --rnd %s --frn %s --grn %s"), key_string, rnd_string, frn_string, grn_string);
+ result = recover_em4x70(&opts, &data);
+ if (PM3_EOVFLOW == result) {
+ PrintAndLogEx(ERR, "Found more than %d potential keys. This is unexpected and likely a code failure.", MAXIMUM_ID48_RECOVERED_KEY_COUNT);
+ return result;
+ } else if (PM3_SUCCESS != result) {
+ PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
+ return result;
+ } else {
+ PrintAndLogEx(INFO, " Found " _GREEN_("%d") " potential keys.", data.potential_key_count);
+ for (uint8_t idx = 0; idx < data.potential_key_count; ++idx) {
+ ID48LIB_KEY q = data.potential_keys[idx];
+ PrintAndLogEx(DEBUG, " Potential Key %d: %s %02X%02X%02X%02X%02X%02X",
+ idx,
+ key_string,
+ q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11]
+ );
+ }
+ last_successful_step = 5;
+ }
+ }
+
+ // 6. Verify which potential key is actually on the tag (using a different rnd/frn combination)
+ // lf em 4x70 auth --rnd --frn
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(INFO, "Step 6. Verify which potential key is actually on the tag");
+ em4x70_cmd_input_verify_auth_t opts_v = {
+ .use_parity = opts.parity,
+ //.rn = {{0}},
+ //.frn = {{0}},
+ //.grn = {{0}},
+ };
+
+ // TODO: retry a few time, if >1 key validated with the new nonce
+ bool continue_loop = true;
+ bool found_one_key = false;
+ bool found_more_than_one_key = false;
+ uint8_t first_validated_key_idx = 0xFF;
+
+ for (uint8_t attempt = 0; continue_loop && (attempt < 10); ++attempt) {
+ continue_loop = false;
+ found_one_key = false;
+ found_more_than_one_key = false;
+ first_validated_key_idx = 0xFF;
+ fill_buffer_prng_bytes(&opts_v.rn, sizeof(ID48LIB_NONCE));
+ for (uint8_t i = 0; i < data.potential_key_count; ++i) {
+ // generate the alternate frn/grn for this key + nonce combo
+ id48lib_generator(&data.potential_keys[i], &opts_v.rn, &opts_v.frn, &opts_v.grn);
+
+ int tmpResult = verify_auth_em4x70(&opts_v);
+ if (PM3_SUCCESS == tmpResult) {
+ if (!found_one_key) {
+ first_validated_key_idx = i;
+ found_one_key = true;
+ } else {
+ found_more_than_one_key = true;
+ }
+ }
+ }
+ if (!found_one_key) {
+ PrintAndLogEx(WARNING, "No potential keys validated. Will try again with different nonce.");
+ continue_loop = true;
+ msleep(2000); // delay 2 seconds ... in case tag was bumped, etc.
+ } else if (found_more_than_one_key) {
+ PrintAndLogEx(WARNING, "Multiple potential keys validated. Will try different nonce.");
+ continue_loop = true;
+ msleep(2000); // delay 2 seconds ... in case tag was bumped, etc.
+ } else {
+ last_successful_step = 6;
+ }
+ }
+ if ((!found_one_key) || found_more_than_one_key) {
+ PrintAndLogEx(FAILED, "Unable to recover any of the multiple potential keys. Check tag for good coupling (position, etc)?");
+ return PM3_EFAILED;
+ } else {
+ // print the validated key to the string buffer (for step 7)
+ ID48LIB_KEY q = data.potential_keys[first_validated_key_idx];
+ snprintf(key_string, 25, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
+ q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
+ q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11]
+ );
+ }
+ }
+ // 7. Print the validated key
+ if (PM3_SUCCESS == result) {
+ PrintAndLogEx(SUCCESS, "Recovered key: " _GREEN_("%s"), key_string);
+ last_successful_step = 7;
+ }
+
+ // For posterity, step 7 used to do the following:
+ // 7. Print the validated key --OR-- Print that the tag is still OK --OR-- Print instructions on what to retry to recover tag to a good state
+ // If success ... print the final key
+ // Else if authentication works with original rnd/frn ... print
+ // Else warn user that the tag is no longer in original state, and print steps to return it to a good state.
+ (void)last_successful_step;
+ return result;
+}
+
+
static command_t CommandTable[] = {
- {"help", CmdHelp, AlwaysAvailable, "This help"},
- {"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
- {"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 key"},
- {"recover", CmdEM4x70Recover, IfPm3EM4x70, "Recover remaining key from partial key"},
+ {"help", CmdHelp, AlwaysAvailable, "This help"},
+ {"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
+ {"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 key"},
+ {"recover", CmdEM4x70Recover, IfPm3EM4x70, "Recover remaining key from partial key"},
+ {"autorecover", CmdEM4x70AutoRecover, IfPm3EM4x70, "Recover entire key from writable tag"},
{NULL, NULL, NULL, NULL}
};
diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h
index 285439318..98b6e5ec6 100644
--- a/client/src/pm3line_vocabulary.h
+++ b/client/src/pm3line_vocabulary.h
@@ -415,19 +415,19 @@ const static vocabulary_t vocabulary[] = {
{ 1, "hf mfu pwdgen" },
{ 0, "hf mfu otptear" },
{ 0, "hf mfu cauth" },
+ { 0, "hf mfu setpwd" },
{ 0, "hf mfu dump" },
{ 0, "hf mfu info" },
{ 0, "hf mfu ndefread" },
{ 0, "hf mfu rdbl" },
{ 0, "hf mfu restore" },
+ { 0, "hf mfu tamper" },
{ 1, "hf mfu view" },
{ 0, "hf mfu wrbl" },
- { 0, "hf mfu tamper" },
{ 0, "hf mfu eload" },
{ 0, "hf mfu esave" },
{ 0, "hf mfu eview" },
{ 0, "hf mfu sim" },
- { 0, "hf mfu setpwd" },
{ 0, "hf mfu setuid" },
{ 0, "hf mfu amiibo" },
{ 1, "hf mfdes help" },
@@ -512,8 +512,6 @@ const static vocabulary_t vocabulary[] = {
{ 1, "hf vas help" },
{ 0, "hf vas reader" },
{ 1, "hf vas decrypt" },
- { 1, "hf waveshare help" },
- { 1, "hf waveshare load" },
{ 1, "hf xerox help" },
{ 1, "hf xerox list" },
{ 0, "hf xerox info" },
@@ -617,6 +615,8 @@ const static vocabulary_t vocabulary[] = {
{ 0, "lf em 4x70 auth" },
{ 0, "lf em 4x70 writepin" },
{ 0, "lf em 4x70 writekey" },
+ { 0, "lf em 4x70 recover" },
+ { 0, "lf em 4x70 autorecover" },
{ 1, "lf fdxb help" },
{ 1, "lf fdxb demod" },
{ 0, "lf fdxb reader" },
diff --git a/doc/commands.json b/doc/commands.json
index c506362f9..401c20e1d 100644
--- a/doc/commands.json
+++ b/doc/commands.json
@@ -3061,7 +3061,7 @@
},
"hf help": {
"command": "hf help",
- "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service } waveshare { Waveshare NFC ePaper... } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
+ "description": "-------- ----------------------- High Frequency ----------------------- 14a { ISO14443A RFIDs... } 14b { ISO14443B RFIDs... } 15 { ISO15693 RFIDs... } cipurse { Cipurse transport Cards... } epa { German Identification Card... } emrtd { Machine Readable Travel Document... } felica { ISO18092 / FeliCa RFIDs... } fido { FIDO and FIDO2 authenticators... } fudan { Fudan RFIDs... } gallagher { Gallagher DESFire RFIDs... } iclass { ICLASS RFIDs... } ict { ICT MFC/DESfire RFIDs... } jooki { Jooki RFIDs... } ksx6924 { KS X 6924 (T-Money, Snapper+) RFIDs } legic { LEGIC RFIDs... } lto { LTO Cartridge Memory RFIDs... } mf { MIFARE RFIDs... } mfp { MIFARE Plus RFIDs... } mfu { MIFARE Ultralight RFIDs... } mfdes { MIFARE Desfire RFIDs... } ntag424 { NXP NTAG 4242 DNA RFIDs... } seos { SEOS RFIDs... } st25ta { ST25TA RFIDs... } tesla { TESLA Cards... } texkom { Texkom RFIDs... } thinfilm { Thinfilm RFIDs... } topaz { TOPAZ (NFC Type 1) RFIDs... } vas { Apple Value Added Service } xerox { Fuji/Xerox cartridge RFIDs... } ----------- --------------------- General --------------------- help This help list List protocol data in trace buffer search Search for known HF tags --------------------------------------------------------------------------------------- hf list available offline: yes Alias of `trace list -t raw` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
"notes": [
"hf list --frame -> show frame delay times",
"hf list -1 -> use trace buffer"
@@ -4411,9 +4411,10 @@
"--1k MIFARE Classic 1k / S50 (def)",
"--2k MIFARE Classic/Plus 2k",
"--4k MIFARE Classic 4k / S70",
- "--ns no save to file"
+ "--ns no save to file",
+ "-v, --verbose verbose output"
],
- "usage": "hf mf dump [-h] [-f ] [-k ] [--mini] [--1k] [--2k] [--4k] [--ns]"
+ "usage": "hf mf dump [-hv] [-f ] [-k ] [--mini] [--1k] [--2k] [--4k] [--ns]"
},
"hf mf ecfill": {
"command": "hf mf ecfill",
@@ -7721,28 +7722,6 @@
],
"usage": "hf vas reader [-h@v] [--pid ] [-f ] [--url ]"
},
- "hf waveshare help": {
- "command": "hf waveshare help",
- "description": "help This help load Load image file to Waveshare NFC ePaper --------------------------------------------------------------------------------------- hf waveshare load available offline: yes Load image file to Waveshare NFC ePaper",
- "notes": [
- "hf waveshare load -f myfile -m 0 -> 2.13 inch e-paper ( 122, 250 )",
- "hf waveshare load -f myfile -m 1 -> 2.9 inch e-paper ( 296, 128 )",
- "hf waveshare load -f myfile -m 2 -> 4.2 inch e-paper ( 400, 300 )",
- "hf waveshare load -f myfile -m 3 -> 7.5 inch e-paper ( 800, 480 )",
- "hf waveshare load -f myfile -m 4 -> 2.7 inch e-paper ( 176, 276 )",
- "hf waveshare load -f myfile -m 5 -> 2.13 inch e-paper B (with red) ( 104, 212 )",
- "hf waveshare load -f myfile -m 6 -> 1.54 inch e-paper B (with red) ( 200, 200 )",
- "hf waveshare load -f myfile -m 7 -> 7.5 inch e-paper HD ( 880, 528 )"
- ],
- "offline": true,
- "options": [
- "-h, --help This help",
- "-m model number [0 - 7] of your tag",
- "-f, --file specify image to upload to tag",
- "-s, --save save paletized version in file"
- ],
- "usage": "hf waveshare load [-h] -m -f [-s ]"
- },
"hf xerox dump": {
"command": "hf xerox dump",
"description": "Dump all memory from a Fuji/Xerox tag ISO/IEC 14443 type B based communications",
@@ -8877,9 +8856,10 @@
},
"lf em 4x70 auth": {
"command": "lf em 4x70 auth",
- "description": "Authenticate against an EM4x70 by sending random number (RN) and F(RN) If F(RN) is incorrect based on the tag crypt key, the tag will not respond",
+ "description": "Authenticate against an EM4x70 by sending random number (RN) and F(RN) If F(RN) is incorrect based on the tag key, the tag will not respond If F(RN) is correct based on the tag key, the tag will give a 20-bit response",
"notes": [
- "lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 -> Test authentication, tag will respond if successful"
+ "lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 -> (using pm3 test key)",
+ "lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0 -> (using research paper key)"
],
"offline": false,
"options": [
@@ -8890,6 +8870,23 @@
],
"usage": "lf em 4x70 auth [-h] [--par] --rnd --frn "
},
+ "lf em 4x70 autorecover": {
+ "command": "lf em 4x70 autorecover",
+ "description": "This command will perform automatic recovery of the key from a writable tag. All steps are possible to do manually. The corresponding sequence, if done manually, is as follows: 1. Verify passed parameters authenticate with the tag (safety check) lf em 4x70 auth --rnd --frn 2. Brute force the key bits in block 9 lf em 4x70 write -b 9 -d 0000 lf em 4x70 recover -b 9 --rnd --frn lf em 4x70 write -b 9 -d 3. Brute force the key bits in block 8 lf em 4x70 write -b 8 -d 0000 lf em 4x70 recover -b 8 --rnd --frn lf em 4x70 write -b 8 -d 4. Brute force the key bits in block 7 lf em 4x70 write -b 7 -d 0000) lf em 4x70 recover -b 7 --rnd --frn lf em 4x70 write -b 7 -d 5. Recover potential values of the lower 48 bits of the key lf em 4x70 recover --key --rnd --frn 6. Verify which potential key is actually on the tag (using a different rnd/frn combination) lf em 4x70 auth --rnd --frn 7. Print the validated key This command simply requires the rnd/frn/grn from a single known-good authentication.",
+ "notes": [
+ "lf em 4x70 autorecover --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)",
+ "lf em 4x70 autorecover --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)"
+ ],
+ "offline": false,
+ "options": [
+ "-h, --help This help",
+ "--par Add parity bit when sending commands",
+ "--rnd Random 56-bit from known-good authentication",
+ "--frn F(RN) 28-bit as 4 hex bytes from known-good authentication",
+ "--grn G(RN) 20-bit as 3 hex bytes from known-good authentication"
+ ],
+ "usage": "lf em 4x70 autorecover [-h] [--par] --rnd --frn --grn "
+ },
"lf em 4x70 help": {
"command": "lf em 4x70 help",
"description": "help This help --------------------------------------------------------------------------------------- lf em 4x70 brute available offline: no Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70 This attack does NOT write anything to the tag. Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'. After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'",
@@ -8921,6 +8918,24 @@
],
"usage": "lf em 4x70 info [-h] [--par]"
},
+ "lf em 4x70 recover": {
+ "command": "lf em 4x70 recover",
+ "description": "After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover key bits 47..00. By default, this process does NOT require a tag to be present. By default, the potential keys are shown (typically 1-6) along with a corresponding 'lf em 4x70 auth' command that will authenticate, if that potential key is correct. The user can copy/paste these commands when the tag is present to manually check which of the potential keys is correct.",
+ "notes": [
+ "lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)",
+ "lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)"
+ ],
+ "offline": false,
+ "options": [
+ "-h, --help This help",
+ "--par Add parity bit when sending commands",
+ "-k, --key Key as 6 hex bytes",
+ "--rnd Random 56-bit",
+ "--frn F(RN) 28-bit as 4 hex bytes",
+ "--grn G(RN) 20-bit as 3 hex bytes"
+ ],
+ "usage": "lf em 4x70 recover [-h] [--par] -k --rnd --frn --grn "
+ },
"lf em 4x70 unlock": {
"command": "lf em 4x70 unlock",
"description": "Unlock EM4x70 by sending PIN Default pin may be: AAAAAAAA 00000000",
@@ -8956,13 +8971,14 @@
"command": "lf em 4x70 writekey",
"description": "Write new 96-bit key to tag",
"notes": [
- "lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B"
+ "lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B (pm3 test key)",
+ "lf em 4x70 writekey -k A090A0A02080000000000000 (research paper key)"
],
"offline": false,
"options": [
"-h, --help This help",
"--par Add parity bit when sending commands",
- "-k, --key Crypt Key as 12 hex bytes"
+ "-k, --key Key as 12 hex bytes"
],
"usage": "lf em 4x70 writekey [-h] [--par] -k "
},
@@ -12513,8 +12529,8 @@
}
},
"metadata": {
- "commands_extracted": 722,
+ "commands_extracted": 723,
"extracted_by": "PM3Help2JSON v1.00",
- "extracted_on": "2024-02-21T21:49:16"
+ "extracted_on": "2024-03-12T00:21:47"
}
}
diff --git a/doc/commands.md b/doc/commands.md
index a394b4e4c..25624d7f0 100644
--- a/doc/commands.md
+++ b/doc/commands.md
@@ -601,20 +601,20 @@ Check column "offline" for their availability.
|`hf mfu keygen `|Y |`Generate DES/3DES/AES MIFARE diversified keys`
|`hf mfu pwdgen `|Y |`Generate pwd from known algos`
|`hf mfu otptear `|N |`Tear-off test on OTP bits`
-|`hf mfu cauth `|N |`Authentication - Ultralight-C`
+|`hf mfu cauth `|N |`Ultralight-C - Authentication`
+|`hf mfu setpwd `|N |`Ultralight-C - Set 3DES key`
|`hf mfu dump `|N |`Dump MIFARE Ultralight family tag to binary file`
|`hf mfu info `|N |`Tag information`
|`hf mfu ndefread `|N |`Prints NDEF records from card`
|`hf mfu rdbl `|N |`Read block`
-|`hf mfu restore `|N |`Restore a dump onto a MFU MAGIC tag`
+|`hf mfu restore `|N |`Restore a dump file onto a tag`
+|`hf mfu tamper `|N |`NTAG 213TT - Configure the tamper feature`
|`hf mfu view `|Y |`Display content from tag dump file`
|`hf mfu wrbl `|N |`Write block`
-|`hf mfu tamper `|N |`Configure the tamper feature on an NTAG 213TT`
|`hf mfu eload `|N |`Upload file into emulator memory`
|`hf mfu esave `|N |`Save emulator memory to file`
|`hf mfu eview `|N |`View emulator memory`
|`hf mfu sim `|N |`Simulate MIFARE Ultralight from emulator memory`
-|`hf mfu setpwd `|N |`Set 3DES key - Ultralight-C`
|`hf mfu setuid `|N |`Set UID - MAGIC tags only`
|`hf mfu amiibo `|N |`Amiibo tag operations`
@@ -773,16 +773,6 @@ Check column "offline" for their availability.
|`hf vas decrypt `|Y |`Decrypt a previously captured VAS cryptogram`
-### hf waveshare
-
- { Waveshare NFC ePaper... }
-
-|command |offline |description
-|------- |------- |-----------
-|`hf waveshare help `|Y |`This help`
-|`hf waveshare load `|Y |`Load image file to Waveshare NFC ePaper`
-
-
### hf xerox
{ Fuji/Xerox cartridge RFIDs... }
@@ -965,13 +955,15 @@ Check column "offline" for their availability.
|command |offline |description
|------- |------- |-----------
|`lf em 4x70 help `|Y |`This help`
-|`lf em 4x70 brute `|N |`Bruteforce EM4X70 to find partial Crypt Key`
+|`lf em 4x70 brute `|N |`Bruteforce EM4X70 to find partial key`
|`lf em 4x70 info `|N |`Tag information EM4x70`
|`lf em 4x70 write `|N |`Write EM4x70`
|`lf em 4x70 unlock `|N |`Unlock EM4x70 for writing`
|`lf em 4x70 auth `|N |`Authenticate EM4x70`
|`lf em 4x70 writepin `|N |`Write PIN`
-|`lf em 4x70 writekey `|N |`Write Crypt Key`
+|`lf em 4x70 writekey `|N |`Write key`
+|`lf em 4x70 recover `|N |`Recover remaining key from partial key`
+|`lf em 4x70 autorecover `|N |`Recover entire key from writable tag`
### lf fdxb
diff --git a/include/common.h b/include/common.h
index b3d4598c5..a2542ef38 100644
--- a/include/common.h
+++ b/include/common.h
@@ -148,16 +148,16 @@ extern bool g_tearoff_enabled;
// convert 2 bytes to U16 in little endian
#ifndef BYTES2UINT16
-# define BYTES2UINT16(x) ((x[1] << 8) | (x[0]))
+# define BYTES2UINT16(x) (((x)[1] << 8) | ((x)[0]))
#endif
// convert 4 bytes to U32 in little endian
#ifndef BYTES2UINT32
-# define BYTES2UINT32(x) ((x[3] << 24) | (x[2] << 16) | (x[1] << 8) | (x[0]))
+# define BYTES2UINT32(x) (((x)[3] << 24) | ((x)[2] << 16) | ((x)[1] << 8) | ((x)[0]))
#endif
// convert 4 bytes to U32 in big endian
#ifndef BYTES2UINT32_BE
-# define BYTES2UINT32_BE(x) ((x[0] << 24) | (x[1] << 16) | (x[2] << 8) | (x[3]))
+# define BYTES2UINT32_BE(x) (((x)[0] << 24) | ((x)[1] << 16) | ((x)[2] << 8) | ((x)[3]))
#endif
@@ -174,7 +174,7 @@ extern bool g_tearoff_enabled;
#endif
#ifndef CRUMB
-# define CRUMB(b,p) (((b & (0x3 << p) ) >> p ) & 0xF)
+# define CRUMB(b,p) ((((b) & (0x3 << (p)) ) >> (p) ) & 0xF)
#endif
#ifndef SWAP_NIBBLE
@@ -183,18 +183,18 @@ extern bool g_tearoff_enabled;
// Binary Encoded Digit
#ifndef BCD2DEC
-# define BCD2DEC(bcd) HornerScheme(bcd, 0x10, 10)
+# define BCD2DEC(bcd) HornerScheme((bcd), 0x10, 10)
#endif
#ifndef DEC2BCD
-# define DEC2BCD(dec) HornerScheme(dec, 10, 0x10)
+# define DEC2BCD(dec) HornerScheme((dec), 10, 0x10)
#endif
// bit stream operations
-#define TEST_BIT(data, i) (*(data + (i / 8)) >> (7 - (i % 8))) & 1
-#define SET_BIT(data, i) *(data + (i / 8)) |= (1 << (7 - (i % 8)))
-#define CLEAR_BIT(data, i) *(data + (i / 8)) &= ~(1 << (7 - (i % 8)))
-#define FLIP_BIT(data, i) *(data + (i / 8)) ^= (1 << (7 - (i % 8)))
+#define TEST_BIT(data, i) (*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1
+#define SET_BIT(data, i) *((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8)))
+#define CLEAR_BIT(data, i) *((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8)))
+#define FLIP_BIT(data, i) *((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8)))
// time for decompressing and loading the image to the FPGA
#define FPGA_LOAD_WAIT_TIME (1500)
diff --git a/include/em4x70.h b/include/em4x70.h
index 0b9bcd2f2..2a11acc02 100644
--- a/include/em4x70.h
+++ b/include/em4x70.h
@@ -30,7 +30,7 @@
typedef struct {
// ISSUE: `bool` type does not have a standard-defined size.
- // therefore, compatibility between architectures /
+ // therefore, compatibility between architectures /
// compilers is not guaranteed.
// ISSUE: C99 has no _Static_assert() ... was added in C11
// TODO: add _Static_assert(sizeof(bool)==1);
@@ -39,6 +39,7 @@ typedef struct {
// Used for writing address
uint8_t address;
+ // ISSUE: Presumes target is little-endian
uint16_t word;
// PIN to unlock
@@ -53,6 +54,7 @@ typedef struct {
uint8_t crypt_key[12];
// used for bruteforce the partial key
+ // ISSUE: Presumes target is little-endian
uint16_t start_key;
} em4x70_data_t;