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..8d85c9b60 100644
--- a/client/src/cmdlfem4x70.c
+++ b/client/src/cmdlfem4x70.c
@@ -25,11 +25,139 @@
#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);
@@ -40,100 +168,284 @@ static void fill_buffer_prng_bytes(void* buffer, size_t byte_count) {
((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);
+
+ clearCommandBuffer();
+ SendCommandNG(CMD_LF_EM4X70_AUTH, (uint8_t *)&etd, sizeof(etd));
- return em4x70_info() == PM3_SUCCESS;
+ 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,59 +784,88 @@ 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;
@@ -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
@@ -689,42 +962,31 @@ int CmdEM4x70Recover(const char *Cmd) {
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];
@@ -754,7 +1016,8 @@ int CmdEM4x70Recover(const char *Cmd) {
}
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") ".");
+ 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/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..9309eee52 100644
--- a/include/em4x70.h
+++ b/include/em4x70.h
@@ -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;