RRG-Proxmark3/client/src/cmdlfem4x70.c

1515 lines
62 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Low frequency EM4x70 commands
//-----------------------------------------------------------------------------
#include "cmdlfem4x70.h"
#include <ctype.h>
#include "cmdparser.h" // command_t
#include "cliparser.h"
#include "fileutils.h"
#include "commonutil.h"
#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 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 {
/// <summary>
/// The full data on an em4170 tag.
/// For V4070 tags:
/// * UM2 does not exist on the tag
/// * Pin does not exist on the tag
/// * UM1 (including the lock bits) might be one-time programmable (OTP)
///
/// [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₀₀
/// </summary>
/// <remarks>
/// When moving to C++, strongly consider adding
/// const helper functions to extract a given
/// block of data into native uint16_t.
/// See also the em4x70_print_info_result() function
/// for visual presentation of the data.
/// </remarks>
uint8_t Raw[32];
} em4x70_tag_info_t;
typedef struct _em4x70_cmd_input_writeblock_t {
uint8_t block;
uint8_t value[2];
} em4x70_cmd_input_writeblock_t;
typedef struct _em4x70_cmd_input_brute_t {
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 {
/// <summary>
/// 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
/// </summary>
uint8_t partial_key[2];
} em4x70_cmd_output_brute_t;
typedef struct _em4x70_cmd_input_unlock_t {
uint8_t pin[4];
} em4x70_cmd_input_unlock_t;
typedef struct _em4x70_cmd_input_auth_t {
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_setpin_t {
uint8_t pin[4];
} em4x70_cmd_input_setpin_t;
typedef struct _em4x70_cmd_input_setkey_t {
ID48LIB_KEY key;
} em4x70_cmd_input_setkey_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 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 {
ID48LIB_NONCE rn;
ID48LIB_FRN frn;
ID48LIB_GRN grn;
} em4x70_cmd_input_verify_auth_t;
typedef struct _em4x70_cmd_input_calculate_t {
ID48LIB_KEY key;
ID48LIB_NONCE rn;
} em4x70_cmd_input_calculate_t;
typedef struct _em4x70_cmd_output_calculate_t {
ID48LIB_FRN frn;
ID48LIB_GRN grn;
} em4x70_cmd_output_calculate_t;
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();
}
}
static void em4x70_print_info_result(const em4x70_tag_info_t *data) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------");
PrintAndLogEx(INFO, "Block | data | info");
PrintAndLogEx(INFO, "------+----------+-----------------------------");
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, "------+----------+-----------------------------");
PrintAndLogEx(INFO, " %2d | -- -- | %s", 11, "PIN write only");
PrintAndLogEx(INFO, " %2d | -- -- | %s", 10, "PIN write only");
PrintAndLogEx(INFO, "------+----------+-----------------------------");
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, "------+----------+-----------------------------");
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, "------+----------+-----------------------------");
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->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, "");
}
static int get_em4x70_info(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 edata = {0};
clearCommandBuffer();
SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(em4x70_data_t));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
}
return resp.status;
}
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 = {
.address = opts->block,
.word = BYTES2UINT16(opts->value),
};
clearCommandBuffer();
SendCommandNG(CMD_LF_EM4X70_WRITE, (uint8_t *)&etd, sizeof(etd));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_EM4X70_WRITE, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
}
return resp.status;
}
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));
// TODO: change firmware to use per-cmd structures
em4x70_data_t etd = {0};
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));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_EM4X70_AUTH, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
// 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 resp.status;
}
static int setkey_em4x70(const em4x70_cmd_input_setkey_t *opts) {
// TODO: change firmware to use per-cmd structures
em4x70_data_t etd = {0};
memcpy(&etd.crypt_key[0], &opts->key.k[0], 12);
clearCommandBuffer();
SendCommandNG(CMD_LF_EM4X70_SETKEY, (uint8_t *)&etd, sizeof(etd));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_EM4X70_SETKEY, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
return resp.status;
}
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.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 == PM3_SUCCESS) {
memcpy(data_out, resp.data.asBytes, sizeof(em4x70_cmd_output_brute_t));
}
return resp.status;
}
// 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.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) == false) {
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
}
return resp.status;
}
static int setpin_em4x70(const em4x70_cmd_input_setpin_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.pin = BYTES2UINT32(opts->pin);
clearCommandBuffer();
SendCommandNG(CMD_LF_EM4X70_SETPIN, (uint8_t *)&etd, sizeof(etd));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_EM4X70_SETPIN, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
}
return resp.status;
}
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 = {
.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;
}
static int CmdEM4x70Info(const char *Cmd) {
// invoke reading of a EM4x70 tag which has to be on the antenna because
// decoding is done by the device (not on client side)
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 info",
"Tag Information EM4x70\n"
" Tag variants include ID48 automotive transponder.\n"
" ID48 does not use command parity (default).\n"
" V4070 and EM4170 do require parity bit.",
"lf em 4x70 info\n"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
// Client command line parsing and validation complete ... now use the helper function
em4x70_tag_info_t info;
int result = get_em4x70_info(&info);
if (result == PM3_ETIMEOUT) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
} else if (result == PM3_SUCCESS) {
em4x70_print_info_result(&info);
} else {
PrintAndLogEx(FAILED, "Reading ( " _RED_("fail") " )");
}
return result;
}
static int CmdEM4x70Write(const char *Cmd) {
// write one block/word (16 bits) to the tag at given block address (0-15)
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 write",
"Write EM4x70\n",
"lf em 4x70 write -b 15 -d c0de -> write 'c0de' to block 15\n"
);
void *argtable[] = {
arg_param_begin,
arg_int1("b", "block", "<dec>", "block/word address, dec"),
arg_str1("d", "data", "<hex>", "data, 2 bytes"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_writeblock_t opts = {
.block = arg_get_int_def(ctx, 1, 1),
.value = {0}, // hex value macro exits function, so cannot be initialized here
};
int value_len = 0;
CLIGetHexWithReturn(ctx, 2, opts.value, &value_len);
CLIParserFree(ctx);
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;
}
// 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, "timeout while waiting for reply");
} else if (result == PM3_SUCCESS) {
em4x70_print_info_result(&info);
} else {
PrintAndLogEx(FAILED, "Writing ( " _RED_("fail") " )");
}
return result;
}
static 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)
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"
"Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'.\n"
"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 (pm3 test key)\n"
"lf em 4x70 brute -b 8 --rnd 3FFE1FB6CC513F --frn F355F1A0 --> bruteforcing key bits k79...k64 (research paper key)\n"
"lf em 4x70 brute -b 7 --rnd 7D5167003571F8 --frn 982DBCC0 --> bruteforcing key bits k63...k48 (autorecovery test key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_int1("b", "block", "<dec>", "block/word address, dec"),
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
arg_str0("s", "start", "<hex>", "Start bruteforce enumeration from this key value"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_brute_t opts = {
.block = arg_get_int_def(ctx, 1, 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
};
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;
}
int rnd_len = 7;
CLIGetHexWithReturn(ctx, 2, opts.rn.rn, &rnd_len);
int frnd_len = 4;
CLIGetHexWithReturn(ctx, 3, 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, 4, 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;
}
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, got %d", rnd_len);
return PM3_EINVARG;
}
if (frnd_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frnd_len);
return PM3_EINVARG;
}
// Client command line parsing and validation complete ... now use the helper function
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to exit");
em4x70_cmd_output_brute_t data;
int result = brute_em4x70(&opts, &data);
if (result == PM3_EOPABORTED) {
PrintAndLogEx(DEBUG, "\naborted via keyboard!");
} 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_("fail") " )");
}
return result;
}
static int CmdEM4x70Unlock(const char *Cmd) {
// send pin code to device, unlocking it for writing
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 unlock",
"Unlock EM4x70 by sending PIN\n"
"Default pin may be:\n"
" AAAAAAAA\n"
" 00000000\n",
"lf em 4x70 unlock -p 11223344 -> Unlock with PIN\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("p", "pin", "<hex>", "pin, 4 bytes"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_unlock_t opts = {
.pin = {0}, // hex value macro exits function, so cannot be initialized here
};
int pin_len = 0;
CLIGetHexWithReturn(ctx, 1, opts.pin, &pin_len);
CLIParserFree(ctx);
if (pin_len != 4) {
PrintAndLogEx(FAILED, "PIN length must be 4 bytes, got %d", pin_len);
return PM3_EINVARG;
}
// 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");
} else if (result == PM3_SUCCESS) {
em4x70_print_info_result(&info);
} else {
PrintAndLogEx(FAILED, "Unlocking tag ( " _RED_("fail") " )");
}
return result;
}
static 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
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 auth",
"Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n"
" If F(RN) is incorrect based on the tag key, the tag will not respond\n"
" If F(RN) is correct based on the tag key, the tag will give a 20-bit response\n",
"lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> (using pm3 test key)\n"
"lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0 --> (using research paper key)\n"
"lf em 4x70 auth --rnd 7D5167003571F8 --frn 982DBCC0 --> (autorecovery test key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_auth_t opts = {
.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, 1, opts.rn.rn, &rn_len);
int frn_len = 4;
CLIGetHexWithReturn(ctx, 2, opts.frn.frn, &frn_len);
CLIParserFree(ctx);
if (rn_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rn_len);
return PM3_EINVARG;
}
if (frn_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
return PM3_EINVARG;
}
// 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");
} else {
PrintAndLogEx(FAILED, "TAG Authentication ( " _RED_("fail") " )");
}
return result;
}
static int CmdEM4x70SetPIN(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 setpin",
"Write new PIN\n",
"lf em 4x70 setpin -p 11223344 -> Write new PIN\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("p", "pin", "<hex>", "pin, 4 bytes"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_setpin_t opts = {
.pin = {0}, // hex value macro exits function, so cannot be initialized here
};
int pin_len = 0;
CLIGetHexWithReturn(ctx, 1, opts.pin, &pin_len);
CLIParserFree(ctx);
if (pin_len != 4) {
PrintAndLogEx(FAILED, "PIN length must be 4 bytes, got %d", pin_len);
return PM3_EINVARG;
}
// Client command line parsing and validation complete ... now use the helper function
em4x70_tag_info_t info;
int result = setpin_em4x70(&opts, &info);
if (result == PM3_ETIMEOUT) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
} else if (result == PM3_SUCCESS) {
em4x70_print_info_result(&info);
PrintAndLogEx(INFO, "Writing new PIN ( " _GREEN_("ok") " )");
} else {
PrintAndLogEx(FAILED, "Writing new PIN ( " _RED_("fail") " )");
}
return result;
}
static int CmdEM4x70SetKey(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 setkey",
"Write new 96-bit key to tag\n",
"lf em 4x70 setkey -k F32AA98CF5BE4ADFA6D3480B (pm3 test key)\n"
"lf em 4x70 setkey -k A090A0A02080000000000000 (research paper key)\n"
"lf em 4x70 setkey -k 022A028C02BE000102030405 (autorecovery test key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("k", "key", "<hex>", "Key as 12 hex bytes"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
em4x70_cmd_input_setkey_t opts = {
.key = {{0}}, // hex value macro exits function, so cannot be initialized here
};
int key_len = 12;
CLIGetHexWithReturn(ctx, 1, opts.key.k, &key_len);
CLIParserFree(ctx);
if (key_len != 12) {
PrintAndLogEx(FAILED, "Key length must be 12 bytes, got %d", key_len);
return PM3_EINVARG;
}
// Client command line parsing and validation complete ... now use the helper function
int result = setkey_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_("fail"));
return result;
}
PrintAndLogEx(INFO, "Writing new key ( " _GREEN_("ok") " )");
// Now verify authentication using the new key, to ensure it was correctly written
em4x70_cmd_input_verify_auth_t opts_v = {
//.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);
// 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_("fail") " )");
return result;
} else {
PrintAndLogEx(INFO, "Authenticating with new key ( " _GREEN_("ok") " )");
}
return result;
}
typedef struct _em4x70_recovery_data_t {
em4x70_cmd_input_recover_t opts;
em4x70_cmd_output_recover_t data;
uint8_t keys_validated_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 CmdEM4x70Recover_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;
CLIParserContext *ctx;
CLIParserInit(
&ctx,
"lf em 4x70 recover",
"After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover\n"
"key bits 47..00. By default, this process does NOT require a tag to be present.\n"
"\n"
"By default, the potential keys are shown (typically 1-6) along with a corresponding\n"
"'lf em 4x70 auth' command that will authenticate, if that potential key is correct.\n"
"The user can copy/paste these commands when the tag is present to manually check\n"
"which of the potential keys is correct.\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 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"
"lf em 4x70 recover --key 022A028C02BE --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("k", "key", "<hex>", "Key as 6 hex bytes"),
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
arg_str1(NULL, "grn", "<hex>", "G(RN) 20-bit as 3 hex bytes"),
//arg_lit0(NULL, "verify", "automatically use tag for validation"),
arg_param_end
};
// do the command line arguments even parse?
if (CLIParserParseString(ctx, Cmd, argtable, arg_getsize(argtable), true)) {
result = PM3_ESOFT;
}
int key_len = 0; // must be 6 bytes hex data
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
// if all OK so far, convert to internal data structure
if (PM3_SUCCESS == result) {
// magic number == index in argtable above. Fragile technique!
if (CLIParamHexToBuf(arg_get_str(ctx, 1), &(out_results->key.k[0]), 12, &key_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 2), &(out_results->nonce.rn[0]), 7, &rnd_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 3), &(out_results->frn.frn[0]), 4, &frn_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 4), &(out_results->grn.grn[0]), 3, &grn_len)) {
result = PM3_ESOFT;
}
//out_results->verify = arg_get_lit(ctx, 6);
}
// if all OK so far, do additional parameter validation
if (PM3_SUCCESS == result) {
// Validate number of bytes read for hex data
if (key_len != 6) {
PrintAndLogEx(FAILED, "Key length must be 6 bytes, got %d", key_len);
result = PM3_EINVARG;
}
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
result = PM3_EINVARG;
}
if (frn_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
result = PM3_EINVARG;
}
if (grn_len != 3) {
PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes, got %d", grn_len);
result = PM3_EINVARG;
}
}
// single exit point
CLIParserFree(ctx);
return result;
}
static int CmdEM4x70Recover(const char *Cmd) {
// From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
// 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 = CmdEM4x70Recover_ParseArgs(Cmd, &recover_ctx.opts);
// recover the potential keys -- no more than a few seconds
if (PM3_SUCCESS == result) {
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);
return result;
} else if (PM3_SUCCESS != result) {
PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
return result;
}
}
// generate alternate authentication for each potential key -- no error paths, sub-second execution
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.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
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.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]
);
}
printf("\n");
// which of those keys actually validates?
if (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.");
// 0. verify a tag is present
// 1. verify the parameters provided authenticate against the tag
// if not, print "Authentication failed. Verify the current tag matches parameters provided."
// print the authentication command used (allows user to easily copy/paste)
// SET ERROR
// 2. for each potential key:
// a. Attempt to authentic against the tag using alt_nonce and alt_frn[i]
// b. verify tag's response is alt_grn[i]
// c. if successful, set ctx.potential_keys_validated[i] = true and increment ctx.keys_validated_count
//
// All validation done... now just interpret the results....
//
// 3. if ctx.keys_validated_count == 0, print "No keys recovered. Check tag for good coupling (position, etc)?"
// 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 setkey -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 <rnd_1> --frn <frn_1>") "\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 <rnd_1> --frn <frn_1>") "\n"
" " _YELLOW_("lf em 4x70 write -b 9 -d <key_block_9>") "\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 <rnd_1> --frn <frn_1>") "\n"
" " _YELLOW_("lf em 4x70 write -b 8 -d <key_block_8>") "\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 <rnd_1> --frn <frn_1>") "\n"
" " _YELLOW_("lf em 4x70 write -b 7 -d <key_block_7>") "\n"
"5. Recover potential values of the lower 48 bits of the key\n"
" " _YELLOW_("lf em 4x70 recover --key <key_block_9><key_block_8><key_block_7> --rnd <rnd_1> --frn <frn_1>") "\n"
"6. Verify which potential key is actually on the tag (using a different rnd/frn combination)\n"
" " _YELLOW_("lf em 4x70 auth --rnd <rnd_2> --frn <frn_N>") "\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"
"lf em 4x70 autorecover --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit from known-good authentication"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes from known-good authentication"),
arg_str1(NULL, "grn", "<hex>", "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
CLIGetHexWithReturn(ctx, 1, out_results->nonce.rn, &rnd_len);
CLIGetHexWithReturn(ctx, 2, out_results->frn.frn, &frn_len);
CLIGetHexWithReturn(ctx, 3, out_results->grn.grn, &grn_len);
CLIParserFree(ctx);
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
result = PM3_EINVARG;
}
if (frn_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
result = PM3_EINVARG;
}
if (grn_len != 3) {
PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes, got %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 <rnd_1> --frn <frn_1>
PrintAndLogEx(INFO, "Step 1. Verifying passed parameters authenticate with the tag (safety check)");
PrintAndLogEx(HINT, "Hint: " _YELLOW_("lf em 4x70 auth --rnd %s --frn %s"), rnd_string, frn_string);
em4x70_cmd_input_auth_t opts_auth = {
.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_("fail") " )");
return result;
} else if (memcmp(&opts.grn, &tag_grn, sizeof(ID48LIB_GRN)) != 0) {
PrintAndLogEx(FAILED, "Authenticating with new key returned %02x %02x %02x"
, tag_grn.grn.grn[0]
, tag_grn.grn.grn[1]
, tag_grn.grn.grn[2]
);
PrintAndLogEx(FAILED, "Expected %s [maybe 5 lsb of key wrong?] ( " _RED_("fail") " )", 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 <rnd_1> --frn <frn_1>
// lf em 4x70 write -b N -d <key_block_N>
for (uint8_t block = 9; 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
PrintAndLogEx(INFO, "Step %d. Brute force the key bits in block %d", step, block);
PrintAndLogEx(HINT, "Hint: " _YELLOW_("lf em 4x70 write -b %d -d 0000"), block);
em4x70_cmd_input_writeblock_t opt_write_zeros = {
.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, "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_("fail") " )", block);
PrintAndLogEx(HINT, "Hint: Block %d data was overwritten. Manually restart at step %d", block, step);
return result;
}
// lf em 4x70 brute -b N --rnd <rnd_1> --frn <frn_1>
PrintAndLogEx(HINT, "Hint: " _YELLOW_("lf em 4x70 brute -b %d --rnd %s --frn %s"), block, rnd_string, frn_string);
em4x70_cmd_input_brute_t opts_brute = {
.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, "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_("fail") " )", block);
PrintAndLogEx(HINT, "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 <key_block_N>
PrintAndLogEx(HINT, "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_zeros2 = {
.block = block,
.value = {brute.partial_key[0], brute.partial_key[1]},
};
result = writeblock_em4x70(&opt_write_zeros2, &tag_info);
if (PM3_ETIMEOUT == result) {
PrintAndLogEx(FAILED, "timeout while waiting for reply");
PrintAndLogEx(HINT, "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_("fail") " )", block);
PrintAndLogEx(HINT, "Hint: Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten", block, brute.partial_key[0], brute.partial_key[1]);
return 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 <key_block_9><key_block_8><key_block_7> --rnd <rnd_1> --frn <frn_1>
PrintAndLogEx(INFO, "Step 5. Recover potential values of the lower 48 bits of the key");
PrintAndLogEx(HINT, "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 <rnd_2> --frn <frn_N>
PrintAndLogEx(INFO, "Step 6. Verify which potential key is actually on the tag");
em4x70_cmd_input_verify_auth_t opts_v = {
//.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 == false) {
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 == false) || found_more_than_one_key) {
PrintAndLogEx(FAILED, "Unable to recover any of the multiple potential keys");
PrintAndLogEx(FAILED, "Check tag for good coupling / position!");
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
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 int CmdEM4x70Calc_ParseArgs(const char *Cmd, em4x70_cmd_input_calculate_t *out_results) {
memset(out_results, 0, sizeof(em4x70_cmd_input_calculate_t));
int result = PM3_SUCCESS;
CLIParserContext *ctx;
CLIParserInit(
&ctx,
"lf em 4x70 calc",
"Calculates both the reader and tag challenge for a user-provided key and rnd.\n"
,
"lf em 4x70 calc --key F32AA98CF5BE4ADFA6D3480B --rnd 45F54ADA252AAC (pm3 test key)\n" // --frn 4866BB70 --grn 9BD180
"lf em 4x70 calc --key A090A0A02080000000000000 --rnd 3FFE1FB6CC513F (research paper key)\n" // --frn F355F1A0 --grn 609D60
"lf em 4x70 calc --key 022A028C02BE000102030405 --rnd 7D5167003571F8 (autorecovery test key)\n" // --frn 982DBCC0 --grn 36C0E0
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "key", "<hex>", "Key 96-bit as 12 hex bytes"),
arg_str1(NULL, "rnd", "<hex>", "56-bit random value sent to tag for authentication"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int key_len = 0; // must be 12 bytes hex data
int rnd_len = 0; // must be 7 bytes hex data
// These macros hide early function return on error ... including free'ing ctx.
CLIGetHexWithReturn(ctx, 1, out_results->key.k, &key_len);
CLIGetHexWithReturn(ctx, 2, out_results->rn.rn, &rnd_len);
CLIParserFree(ctx);
if (key_len != 12) {
PrintAndLogEx(FAILED, "Key length must be 12 bytes, got %d", key_len);
result = PM3_EINVARG;
}
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
result = PM3_EINVARG;
}
return result;
}
static int CmdEM4x70Calc(const char *Cmd) {
em4x70_cmd_input_calculate_t opts = {0};
em4x70_cmd_output_calculate_t data = {0};
// 0. Parse the command line
int result = CmdEM4x70Calc_ParseArgs(Cmd, &opts);
if (PM3_SUCCESS != result) {
return result;
}
// There are no failure paths. All inputs are valid, and ID48LIB doesn't add any failure paths.
id48lib_generator(&opts.key, &opts.rn, &data.frn, &data.grn);
char key_string[24 + 1] = {0};
char rnd_string[14 + 1] = {0};
char frn_string[ 8 + 1] = {0};
char grn_string[ 6 + 1] = {0};
if (true) {
snprintf(
key_string, 25,
"%02X%02X%02X%02X%02X%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]
);
snprintf(
rnd_string, 15,
"%02X%02X%02X%02X%02X%02X%02X",
opts.rn.rn[0], opts.rn.rn[1], opts.rn.rn[2], opts.rn.rn[3], opts.rn.rn[4], opts.rn.rn[5], opts.rn.rn[6]
);
snprintf(
frn_string, 9,
"%02X%02X%02X%02X",
data.frn.frn[0], data.frn.frn[1], data.frn.frn[2], data.frn.frn[3]
);
snprintf(
grn_string, 7,
"%02X%02X%02X",
data.grn.grn[0], data.grn.grn[1], data.grn.grn[2]
);
}
PrintAndLogEx(SUCCESS, "KEY: %s RND: %s FRN: " _GREEN_("%s") " GRN: " _GREEN_("%s"), key_string, rnd_string, frn_string, grn_string);
return PM3_SUCCESS;
}
// Must be declared to be used in the table,
// but cannot be defined yet because it uses the table.
static int CmdHelp(const char *Cmd);
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
{"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information"},
{"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"},
{"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"},
{"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"},
{"setpin", CmdEM4x70SetPIN, IfPm3EM4x70, "Write PIN"},
{"setkey", CmdEM4x70SetKey, IfPm3EM4x70, "Write key"},
{"calc", CmdEM4x70Calc, AlwaysAvailable, "Calculate EM4x70 challenge and response"},
{"recover", CmdEM4x70Recover, AlwaysAvailable, "Recover remaining key from partial key"},
{"autorecover", CmdEM4x70AutoRecover, IfPm3EM4x70, "Recover entire key from writable tag"},
{NULL, NULL, NULL, NULL}
};
static int CmdHelp(const char *Cmd) {
(void)Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
// Only two functions need to be non-static:
// * CmdLFEM4X70()
// * detect_4x70_block()
int CmdLFEM4X70(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}
// 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;
int result = get_em4x70_info(&info);
if (result == PM3_ETIMEOUT) { // consider removing this output?
PrintAndLogEx(WARNING, "timeout while waiting for reply");
}
return result == PM3_SUCCESS;
}