mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Merge pull request #2385 from henrygab/em4x70_add_tests
add `lf em 4x70 calc` and self-tests
This commit is contained in:
+134
-26
@@ -164,7 +164,15 @@ typedef struct _em4x70_cmd_input_verify_auth_t {
|
||||
ID48LIB_GRN grn;
|
||||
} em4x70_cmd_input_verify_auth_t;
|
||||
|
||||
static int CmdHelp(const char *Cmd);
|
||||
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) {
|
||||
@@ -431,23 +439,8 @@ static int verify_auth_em4x70(const em4x70_cmd_input_verify_auth_t *opts) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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, "Timeout while waiting for reply.");
|
||||
}
|
||||
return result == PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int CmdEM4x70Info(const char *Cmd) {
|
||||
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)
|
||||
@@ -487,7 +480,7 @@ int CmdEM4x70Info(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70Write(const char *Cmd) {
|
||||
static int CmdEM4x70Write(const char *Cmd) {
|
||||
|
||||
// write one block/word (16 bits) to the tag at given block address (0-15)
|
||||
CLIParserContext *ctx;
|
||||
@@ -539,7 +532,7 @@ int CmdEM4x70Write(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70Brute(const char *Cmd) {
|
||||
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)
|
||||
@@ -549,8 +542,10 @@ int CmdEM4x70Brute(const char *Cmd) {
|
||||
"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\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_lit0(NULL, "par", "Add parity bit when sending commands"),
|
||||
@@ -623,7 +618,7 @@ int CmdEM4x70Brute(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70Unlock(const char *Cmd) {
|
||||
static int CmdEM4x70Unlock(const char *Cmd) {
|
||||
|
||||
// send pin code to device, unlocking it for writing
|
||||
CLIParserContext *ctx;
|
||||
@@ -671,7 +666,7 @@ int CmdEM4x70Unlock(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70Auth(const char *Cmd) {
|
||||
static int CmdEM4x70Auth(const char *Cmd) {
|
||||
|
||||
// Authenticate transponder
|
||||
// Send 56-bit random number + pre-computed f(rnd, k) to transponder.
|
||||
@@ -731,7 +726,7 @@ int CmdEM4x70Auth(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70SetPIN(const char *Cmd) {
|
||||
static int CmdEM4x70SetPIN(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "lf em 4x70 setpin",
|
||||
"Write new PIN\n",
|
||||
@@ -776,7 +771,7 @@ int CmdEM4x70SetPIN(const char *Cmd) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70SetKey(const char *Cmd) {
|
||||
static int CmdEM4x70SetKey(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "lf em 4x70 setkey",
|
||||
"Write new 96-bit key to tag\n",
|
||||
@@ -902,6 +897,7 @@ static int CmdEM4x70Recover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_
|
||||
,
|
||||
"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[] = {
|
||||
@@ -973,7 +969,7 @@ static int CmdEM4x70Recover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_
|
||||
return result;
|
||||
}
|
||||
|
||||
int CmdEM4x70Recover(const char *Cmd) {
|
||||
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};
|
||||
@@ -1442,6 +1438,97 @@ static int CmdEM4x70AutoRecover(const char *Cmd) {
|
||||
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"},
|
||||
@@ -1452,6 +1539,7 @@ static command_t CommandTable[] = {
|
||||
{"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}
|
||||
@@ -1463,7 +1551,27 @@ static int CmdHelp(const char *Cmd) {
|
||||
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;
|
||||
em4x70_cmd_input_info_t opts = { 0 };
|
||||
|
||||
int result = get_em4x70_info(&opts, &info);
|
||||
|
||||
if (result == PM3_ETIMEOUT) { // consider removing this output?
|
||||
PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
|
||||
}
|
||||
return result == PM3_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
#define TIMEOUT 2000
|
||||
|
||||
int CmdLFEM4X70(const char *Cmd);
|
||||
int CmdEM4x70Info(const char *Cmd);
|
||||
int CmdEM4x70Write(const char *Cmd);
|
||||
int CmdEM4x70Brute(const char *Cmd);
|
||||
int CmdEM4x70Unlock(const char *Cmd);
|
||||
int CmdEM4x70Auth(const char *Cmd);
|
||||
int CmdEM4x70SetPIN(const char *Cmd);
|
||||
int CmdEM4x70SetKey(const char *Cmd);
|
||||
int CmdEM4x70Recover(const char *Cmd);
|
||||
|
||||
// for `lf search`:
|
||||
bool detect_4x70_block(void);
|
||||
|
||||
Reference in New Issue
Block a user