recover_pk.py: replace secp192r1 by prime192v1
[RRG-proxmark3.git] / client / src / cmdlfem4x70.c
blobcffac044d7eacf3e453ceb6f88999719d454bd2a
1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // Low frequency EM4x70 commands
17 //-----------------------------------------------------------------------------
19 #include "cmdlfem4x70.h"
20 #include <ctype.h>
21 #include "cmdparser.h" // command_t
22 #include "cliparser.h"
23 #include "fileutils.h"
24 #include "commonutil.h"
25 #include "em4x70.h"
26 #include "id48.h"
27 #include "time.h"
28 #include "util_posix.h" // msleep()
30 #define LOCKBIT_0 BITMASK(6)
31 #define LOCKBIT_1 BITMASK(7)
33 #define BYTE_ARRAY_INDEX_TO_BLOCK(x) ((31-(x))/2)
35 // TODO: Optional: use those unique structures in a union, call it em4x70_data_t, but add a first
36 // common header field that includes the command itself (to improve debugging / validation).
37 typedef struct _em4x70_tag_info_t {
38 /// <summary>
39 /// The full data on an em4x70 the tag.
40 /// [31] == Block 15 MSB == UM2₆₃..UM2₅₆
41 /// [30] == Block 15 LSB == UM2₅₅..UM2₄₈
42 /// [29] == Block 14 MSB == UM2₄₇..UM2₄₀
43 /// [28] == Block 14 LSB == UM2₃₉..UM2₃₂
44 /// [27] == Block 13 MSB == UM2₃₁..UM2₂₄
45 /// [26] == Block 13 LSB == UM2₂₃..UM2₁₆
46 /// [25] == Block 12 MSB == UM2₁₅..UM2₀₈
47 /// [24] == Block 12 LSB == UM2₀₇..UM2₀₀
48 /// [23] == Block 11 MSB == Pin₃₁..Pin₂₄
49 /// [22] == Block 11 LSB == Pin₂₃..Pin₁₆
50 /// [21] == Block 10 MSB == Pin₁₅..Pin₀₈
51 /// [20] == Block 10 LSB == Pin₀₇..Pin₀₀
52 /// [19] == Block 9 MSB == Key₉₅..Key₈₈
53 /// [18] == Block 9 LSB == Key₈₇..Key₈₀
54 /// [17] == Block 8 MSB == Key₇₉..Key₇₂
55 /// [16] == Block 8 LSB == Key₇₁..Key₆₄
56 /// [15] == Block 7 MSB == Key₆₃..Key₅₆
57 /// [14] == Block 7 LSB == Key₅₅..Key₄₈
58 /// [13] == Block 6 MSB == Key₄₇..Key₄₀
59 /// [12] == Block 6 LSB == Key₃₉..Key₃₂
60 /// [11] == Block 5 MSB == Key₃₁..Key₂₄
61 /// [10] == Block 5 LSB == Key₂₃..Key₁₆
62 /// [ 9] == Block 4 MSB == Key₁₅..Key₀₈
63 /// [ 8] == Block 4 LSB == Key₀₇..Key₀₀
64 /// [ 7] == Block 3 MSB == ID₃₁..ID₂₄
65 /// [ 6] == Block 3 LSB == ID₂₃..ID₁₆
66 /// [ 5] == Block 2 MSB == ID₁₅..ID₀₈
67 /// [ 4] == Block 2 LSB == ID₀₇..ID₀₀
68 /// [ 3] == Block 1 MSB == L₁ L₀ UM1₂₉..UM1₂₄
69 /// [ 2] == Block 1 LSB == UM1₂₃..UM1₁₆
70 /// [ 1] == Block 0 MSB == UM1₁₅..UM1₀₈
71 /// [ 0] == Block 0 LSB == UM1₀₇..UM1₀₀
72 /// </summary>
73 /// <remarks>
74 /// When moving to C++, strongly consider adding
75 /// const helper functions to extract a given
76 /// block of data into native uint16_t.
77 /// See also the em4x70_print_info_result() function
78 /// for visual presentation of the data.
79 /// </remarks>
80 uint8_t Raw[32];
81 } em4x70_tag_info_t;
83 typedef struct _em4x70_cmd_input_info_t {
84 uint8_t use_parity;
85 } em4x70_cmd_input_info_t;
87 typedef struct _em4x70_cmd_input_writeblock_t {
88 uint8_t use_parity;
89 uint8_t block;
90 uint8_t value[2];
91 } em4x70_cmd_input_writeblock_t;
93 typedef struct _em4x70_cmd_input_brute_t {
94 uint8_t use_parity;
95 ID48LIB_NONCE rn;
96 ID48LIB_FRN frn;
97 uint8_t block;
98 uint8_t partial_key_start[2];
99 } em4x70_cmd_input_brute_t;
101 typedef struct _em4x70_cmd_output_brute_t {
102 /// <summary>
103 /// The returned data is big endian (MSB first).
104 /// For block 9:
105 /// partial_key[0] == Key₉₅..Key₈₈ == Block 9 MSB
106 /// partial_key[1] == Key₈₇..Key₈₀ == Block 9 LSB
107 /// For block 8:
108 /// partial_key[0] == Key₇₉..Key₇₂ == Block 8 MSB
109 /// partial_key[1] == Key₇₁..Key₆₄ == Block 8 LSB
110 /// For block 7:
111 /// partial_key[15] == Key₆₃..Key₅₆ == Block 7 MSB
112 /// partial_key[14] == Key₅₅..Key₄₈ == Block 7 LSB
113 /// </summary>
114 uint8_t partial_key[2];
115 } em4x70_cmd_output_brute_t;
117 typedef struct _em4x70_cmd_input_unlock_t {
118 uint8_t use_parity;
119 uint8_t pin[4];
120 } em4x70_cmd_input_unlock_t;
122 typedef struct _em4x70_cmd_input_auth_t {
123 uint8_t use_parity;
124 ID48LIB_NONCE rn;
125 ID48LIB_FRN frn;
126 } em4x70_cmd_input_auth_t;
128 typedef struct _em4x70_cmd_output_auth_t {
129 ID48LIB_GRN grn;
130 } em4x70_cmd_output_auth_t;
132 typedef struct _em4x70_cmd_input_setpin_t {
133 uint8_t use_parity;
134 uint8_t pin[4];
135 } em4x70_cmd_input_setpin_t;
137 typedef struct _em4x70_cmd_input_setkey_t {
138 uint8_t use_parity;
139 ID48LIB_KEY key;
140 } em4x70_cmd_input_setkey_t;
142 // There is no output data when writing a new key
143 typedef struct _em4x70_cmd_input_recover_t {
144 ID48LIB_KEY key; // only the first 6 bytes (48 bits) are considered valid
145 ID48LIB_NONCE nonce;
146 ID48LIB_FRN frn;
147 ID48LIB_GRN grn;
148 bool parity; // if true, add parity bit to commands sent to tag
149 bool verify; // if true, tag must be present
150 } em4x70_cmd_input_recover_t;
152 // largest seen "in the wild" was 6
153 #define MAXIMUM_ID48_RECOVERED_KEY_COUNT 10
155 typedef struct _em4x70_cmd_output_recover_t {
156 uint8_t potential_key_count;
157 ID48LIB_KEY potential_keys[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
158 } em4x70_cmd_output_recover_t;
160 typedef struct _em4x70_cmd_input_verify_auth_t {
161 uint8_t use_parity;
162 ID48LIB_NONCE rn;
163 ID48LIB_FRN frn;
164 ID48LIB_GRN grn;
165 } em4x70_cmd_input_verify_auth_t;
167 typedef struct _em4x70_cmd_input_calculate_t {
168 ID48LIB_KEY key;
169 ID48LIB_NONCE rn;
170 } em4x70_cmd_input_calculate_t;
171 typedef struct _em4x70_cmd_output_calculate_t {
172 ID48LIB_FRN frn;
173 ID48LIB_GRN grn;
174 } em4x70_cmd_output_calculate_t;
177 static void fill_buffer_prng_bytes(void *buffer, size_t byte_count) {
178 if (byte_count == 0) {
179 return;
182 srand((unsigned) time(NULL));
183 for (size_t i = 0; i < byte_count; i++) {
184 ((uint8_t *)buffer)[i] = (uint8_t)rand();
188 static void em4x70_print_info_result(const em4x70_tag_info_t *data) {
189 PrintAndLogEx(NORMAL, "");
190 PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------");
191 PrintAndLogEx(INFO, "Block | data | info");
192 PrintAndLogEx(INFO, "------+----------+-----------------------------");
193 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 15, data->Raw[31], data->Raw[30], "UM2");
194 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 14, data->Raw[29], data->Raw[28], "UM2");
195 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 13, data->Raw[27], data->Raw[26], "UM2");
196 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 12, data->Raw[25], data->Raw[24], "UM2");
197 PrintAndLogEx(INFO, "------+----------+-----------------------------");
198 PrintAndLogEx(INFO, " %2d | -- -- | %s", 11, "PIN write only");
199 PrintAndLogEx(INFO, " %2d | -- -- | %s", 10, "PIN write only");
200 PrintAndLogEx(INFO, "------+----------+-----------------------------");
201 PrintAndLogEx(INFO, " %2d | -- -- | %s", 9, "KEY write only");
202 PrintAndLogEx(INFO, " %2d | -- -- | %s", 8, "KEY write only");
203 PrintAndLogEx(INFO, " %2d | -- -- | %s", 7, "KEY write only");
204 PrintAndLogEx(INFO, " %2d | -- -- | %s", 6, "KEY write only");
205 PrintAndLogEx(INFO, " %2d | -- -- | %s", 5, "KEY write only");
206 PrintAndLogEx(INFO, " %2d | -- -- | %s", 4, "KEY write only");
207 PrintAndLogEx(INFO, "------+----------+-----------------------------");
208 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 3, data->Raw[ 7], data->Raw[ 6], "ID");
209 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 2, data->Raw[ 5], data->Raw[ 4], "ID");
210 PrintAndLogEx(INFO, "------+----------+-----------------------------");
211 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 1, data->Raw[ 3], data->Raw[ 2], "UM1");
212 PrintAndLogEx(INFO, " %2d | %02X %02X | %s", 0, data->Raw[ 1], data->Raw[ 0], "UM1");
213 PrintAndLogEx(INFO, "------+----------+-----------------------------");
214 PrintAndLogEx(INFO, "");
216 PrintAndLogEx(INFO, "Tag ID: %02X %02X %02X %02X", data->Raw[7], data->Raw[6], data->Raw[5], data->Raw[4]);
217 PrintAndLogEx(INFO, "Lockbit 0: %d", (data->Raw[3] & LOCKBIT_0) ? 1 : 0);
218 PrintAndLogEx(INFO, "Lockbit 1: %d", (data->Raw[3] & LOCKBIT_1) ? 1 : 0);
219 PrintAndLogEx(INFO, "Tag is %s.", (data->Raw[3] & LOCKBIT_0) ? _RED_("LOCKED") : _GREEN_("UNLOCKED"));
220 PrintAndLogEx(INFO, "");
221 PrintAndLogEx(NORMAL, "");
224 static int get_em4x70_info(const em4x70_cmd_input_info_t *opts, em4x70_tag_info_t *data_out) {
226 memset(data_out, 0, sizeof(em4x70_tag_info_t));
228 // TODO: change firmware to use per-cmd structures
229 em4x70_data_t edata = { .parity = opts->use_parity };
230 clearCommandBuffer();
231 SendCommandNG(CMD_LF_EM4X70_INFO, (uint8_t *)&edata, sizeof(em4x70_data_t));
232 PacketResponseNG resp;
233 if (WaitForResponseTimeout(CMD_LF_EM4X70_INFO, &resp, TIMEOUT) == false) {
234 return PM3_ETIMEOUT;
236 if (resp.status == PM3_SUCCESS) {
237 memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
239 return resp.status;
242 static int writeblock_em4x70(const em4x70_cmd_input_writeblock_t *opts, em4x70_tag_info_t *data_out) {
244 memset(data_out, 0, sizeof(em4x70_tag_info_t));
246 // TODO: change firmware to use per-cmd structures
247 em4x70_data_t etd = {0};
248 etd.address = opts->block;
249 etd.word = BYTES2UINT16(opts->value);
250 etd.parity = opts->use_parity;
252 clearCommandBuffer();
253 SendCommandNG(CMD_LF_EM4X70_WRITE, (uint8_t *)&etd, sizeof(etd));
254 PacketResponseNG resp;
255 if (WaitForResponseTimeout(CMD_LF_EM4X70_WRITE, &resp, TIMEOUT) == false) {
256 return PM3_ETIMEOUT;
258 if (resp.status == PM3_SUCCESS) {
259 memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
261 return resp.status;
264 static int auth_em4x70(const em4x70_cmd_input_auth_t *opts, em4x70_cmd_output_auth_t *data_out) {
265 memset(data_out, 0, sizeof(ID48LIB_GRN));
267 // TODO: change firmware to use per-cmd structures
268 em4x70_data_t etd = {0};
269 etd.parity = opts->use_parity;
270 memcpy(&etd.rnd[0], &opts->rn.rn[0], 7);
271 memcpy(&etd.frnd[0], &opts->frn.frn[0], 4);
273 clearCommandBuffer();
274 SendCommandNG(CMD_LF_EM4X70_AUTH, (uint8_t *)&etd, sizeof(etd));
275 PacketResponseNG resp;
276 if (WaitForResponseTimeout(CMD_LF_EM4X70_AUTH, &resp, TIMEOUT) == false) {
277 return PM3_ETIMEOUT;
279 if (resp.status == PM3_SUCCESS) {
280 // Response is 20-bit from tag
281 // HACKHACK -- It appears the byte order differs from what is expected?
282 data_out->grn.grn[0] = resp.data.asBytes[2];
283 data_out->grn.grn[1] = resp.data.asBytes[1];
284 data_out->grn.grn[2] = resp.data.asBytes[0];
285 //memcpy(data_out, &resp.data.asBytes[0], sizeof(ID48LIB_GRN));
287 return resp.status;
290 static int setkey_em4x70(const em4x70_cmd_input_setkey_t *opts) {
292 // TODO: change firmware to use per-cmd structures
293 em4x70_data_t etd = {0};
294 etd.parity = opts->use_parity;
295 memcpy(&etd.crypt_key[0], &opts->key.k[0], 12);
297 clearCommandBuffer();
298 SendCommandNG(CMD_LF_EM4X70_SETKEY, (uint8_t *)&etd, sizeof(etd));
299 PacketResponseNG resp;
300 if (WaitForResponseTimeout(CMD_LF_EM4X70_SETKEY, &resp, TIMEOUT) == false) {
301 return PM3_ETIMEOUT;
303 return resp.status;
306 static int brute_em4x70(const em4x70_cmd_input_brute_t *opts, em4x70_cmd_output_brute_t *data_out) {
307 memset(data_out, 0, sizeof(em4x70_cmd_output_brute_t));
309 // TODO: change firmware to use per-cmd structures
310 em4x70_data_t etd = {0};
311 etd.parity = opts->use_parity;
312 etd.address = opts->block;
313 memcpy(&etd.rnd[0], &opts->rn.rn[0], 7);
314 memcpy(&etd.frnd[0], &opts->frn.frn[0], 4);
316 // TODO: FIX THIS MESS WITH BYTE ORDER CHANGING BACK AND FORTH!
317 // Just use byte arrays when sending to the firmware.
318 // Lowers the cognitive load AND makes it easier to understand.
319 // opts structure stored value in BIG ENDIAN
320 // Note that the FIRMWARE side will swap the byte order back to BIG ENDIAN.
321 // (yes, this is a bit of a mess, but it is what it is for now...)
322 uint16_t start_key_be = (opts->partial_key_start[0] << 8) | opts->partial_key_start[1];
323 etd.start_key = start_key_be;
325 clearCommandBuffer();
326 PacketResponseNG resp;
327 SendCommandNG(CMD_LF_EM4X70_BRUTE, (uint8_t *)&etd, sizeof(etd));
329 uint32_t timeout = 0;
330 for (;;) {
332 if (kbd_enter_pressed()) {
333 SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
334 return PM3_EOPABORTED;
337 if (WaitForResponseTimeout(CMD_LF_EM4X70_BRUTE, &resp, TIMEOUT)) {
338 if (resp.status == PM3_SUCCESS) {
339 memcpy(data_out, resp.data.asBytes, sizeof(em4x70_cmd_output_brute_t));
341 return resp.status;
344 // NOTE: It takes about 11 seconds per 0x0100 authentication attempts.
345 // Thus, each block takes a maximum of 256 * 11 seconds == 46m56s.
346 // A timeout of 60 minutes corresponds to ~14 seconds per 0x0100 auths,
347 // which is ~25% margin. Plus, on average, it takes half that
348 // amount of time (for a random value in the key block).
349 if (timeout > ((60u * 60000u) / TIMEOUT)) {
350 PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
351 return PM3_ETIMEOUT;
353 timeout++;
357 static int unlock_em4x70(const em4x70_cmd_input_unlock_t *opts, em4x70_tag_info_t *data_out) {
358 memset(data_out, 0, sizeof(em4x70_tag_info_t));
360 // TODO: change firmware to use per-cmd structures
361 em4x70_data_t etd = {0};
362 etd.parity = opts->use_parity;
363 etd.pin = BYTES2UINT32(opts->pin);
365 clearCommandBuffer();
366 SendCommandNG(CMD_LF_EM4X70_UNLOCK, (uint8_t *)&etd, sizeof(etd));
367 PacketResponseNG resp;
368 if (WaitForResponseTimeout(CMD_LF_EM4X70_UNLOCK, &resp, TIMEOUT) == false) {
369 return PM3_ETIMEOUT;
371 if (resp.status == PM3_SUCCESS) {
372 memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
374 return resp.status;
377 static int setpin_em4x70(const em4x70_cmd_input_setpin_t *opts, em4x70_tag_info_t *data_out) {
378 memset(data_out, 0, sizeof(em4x70_tag_info_t));
380 // TODO: change firmware to use per-cmd structures
381 em4x70_data_t etd = {0};
382 etd.parity = opts->use_parity;
383 etd.pin = BYTES2UINT32(opts->pin);
385 clearCommandBuffer();
386 SendCommandNG(CMD_LF_EM4X70_SETPIN, (uint8_t *)&etd, sizeof(etd));
387 PacketResponseNG resp;
388 if (WaitForResponseTimeout(CMD_LF_EM4X70_SETPIN, &resp, TIMEOUT) == false) {
389 return PM3_ETIMEOUT;
391 if (resp.status == PM3_SUCCESS) {
392 memcpy(data_out, resp.data.asBytes, sizeof(em4x70_tag_info_t));
394 return resp.status;
397 static int recover_em4x70(const em4x70_cmd_input_recover_t *opts, em4x70_cmd_output_recover_t *data_out) {
398 memset(data_out, 0, sizeof(em4x70_cmd_output_recover_t));
400 // The library is stateful. First must initialize its internal context.
401 id48lib_key_recovery_init(&opts->key, &opts->nonce, &opts->frn, &opts->grn);
403 // repeatedly call id48lib_key_recovery_next() to get the next potential key
404 ID48LIB_KEY q;
405 int result = PM3_SUCCESS;
407 while ((PM3_SUCCESS == result) && id48lib_key_recovery_next(&q)) {
409 if (data_out->potential_key_count >= MAXIMUM_ID48_RECOVERED_KEY_COUNT) {
410 result = PM3_EOVFLOW;
411 } else {
412 data_out->potential_keys[data_out->potential_key_count] = q;
413 ++data_out->potential_key_count;
417 if ((PM3_SUCCESS == result) && (data_out->potential_key_count == 0)) {
418 result = PM3_EFAILED;
420 return result;
423 static int verify_auth_em4x70(const em4x70_cmd_input_verify_auth_t *opts) {
424 em4x70_cmd_input_auth_t opts_auth = {
425 .use_parity = opts->use_parity,
426 .rn = opts->rn,
427 .frn = opts->frn,
430 em4x70_cmd_output_auth_t tag_grn;
432 int result = auth_em4x70(&opts_auth, &tag_grn);
434 if (PM3_SUCCESS == result) {
435 if (memcmp(&opts->grn, &tag_grn, sizeof(ID48LIB_GRN)) != 0) {
436 result = PM3_EWRONGANSWER;
439 return result;
443 static int CmdEM4x70Info(const char *Cmd) {
445 // invoke reading of a EM4x70 tag which has to be on the antenna because
446 // decoding is done by the device (not on client side)
447 CLIParserContext *ctx;
448 CLIParserInit(&ctx, "lf em 4x70 info",
449 "Tag Information EM4x70\n"
450 " Tag variants include ID48 automotive transponder.\n"
451 " ID48 does not use command parity (default).\n"
452 " V4070 and EM4170 do require parity bit.",
453 "lf em 4x70 info\n"
454 "lf em 4x70 info --par -> adds parity bit to command\n"
457 void *argtable[] = {
458 arg_param_begin,
459 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
460 arg_param_end
463 CLIExecWithReturn(ctx, Cmd, argtable, true);
464 em4x70_cmd_input_info_t opts = {
465 .use_parity = arg_get_lit(ctx, 0),
467 CLIParserFree(ctx);
469 // Client command line parsing and validation complete ... now use the helper function
470 em4x70_tag_info_t info;
471 int result = get_em4x70_info(&opts, &info);
473 if (result == PM3_ETIMEOUT) {
474 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
475 } else if (result == PM3_SUCCESS) {
476 em4x70_print_info_result(&info);
477 } else {
478 PrintAndLogEx(FAILED, "Reading ( " _RED_("fail") " )");
480 return result;
483 static int CmdEM4x70Write(const char *Cmd) {
485 // write one block/word (16 bits) to the tag at given block address (0-15)
486 CLIParserContext *ctx;
487 CLIParserInit(&ctx, "lf em 4x70 write",
488 "Write EM4x70\n",
489 "lf em 4x70 write -b 15 -d c0de -> write 'c0de' to block 15\n"
490 "lf em 4x70 write -b 15 -d c0de --par -> adds parity bit to commands\n"
493 void *argtable[] = {
494 arg_param_begin,
495 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
496 arg_int1("b", "block", "<dec>", "block/word address, dec"),
497 arg_str1("d", "data", "<hex>", "data, 2 bytes"),
498 arg_param_end
501 CLIExecWithReturn(ctx, Cmd, argtable, true);
503 em4x70_cmd_input_writeblock_t opts = {
504 .use_parity = arg_get_lit(ctx, 1),
505 .block = arg_get_int_def(ctx, 2, 1),
506 .value = {0}, // hex value macro exits function, so cannot be initialized here
508 int value_len = 0;
509 CLIGetHexWithReturn(ctx, 3, opts.value, &value_len);
510 CLIParserFree(ctx);
512 if (opts.block >= EM4X70_NUM_BLOCKS) {
513 PrintAndLogEx(FAILED, "block has to be within range [0, 15], got %d", opts.block);
514 return PM3_EINVARG;
516 if (value_len != 2) {
517 PrintAndLogEx(FAILED, "word/data length must be 2 bytes, got %d", value_len);
518 return PM3_EINVARG;
521 // Client command line parsing and validation complete ... now use the helper function
522 em4x70_tag_info_t info;
523 int result = writeblock_em4x70(&opts, &info);
525 if (result == PM3_ETIMEOUT) {
526 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
527 } else if (result == PM3_SUCCESS) {
528 em4x70_print_info_result(&info);
529 } else {
530 PrintAndLogEx(FAILED, "Writing ( " _RED_("fail") " )");
532 return result;
535 static int CmdEM4x70Brute(const char *Cmd) {
537 // From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
538 // Partial Key-Update Attack (optimized version)
539 CLIParserContext *ctx;
540 CLIParserInit(&ctx, "lf em 4x70 brute",
541 "Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70\n"
542 "This attack does NOT write anything to the tag.\n"
543 "Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'.\n"
544 "After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'\n",
545 "lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 --> bruteforcing key bits k95...k80 (pm3 test key)\n"
546 "lf em 4x70 brute -b 8 --rnd 3FFE1FB6CC513F --frn F355F1A0 --> bruteforcing key bits k79...k64 (research paper key)\n"
547 "lf em 4x70 brute -b 7 --rnd 7D5167003571F8 --frn 982DBCC0 --> bruteforcing key bits k63...k48 (autorecovery test key)\n"
549 void *argtable[] = {
550 arg_param_begin,
551 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
552 arg_int1("b", "block", "<dec>", "block/word address, dec"),
553 arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
554 arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
555 arg_str0("s", "start", "<hex>", "Start bruteforce enumeration from this key value"),
556 arg_param_end
558 CLIExecWithReturn(ctx, Cmd, argtable, true);
560 em4x70_cmd_input_brute_t opts = {
561 .use_parity = arg_get_lit(ctx, 1),
562 .block = arg_get_int_def(ctx, 2, 0),
563 .rn = {{0}}, // hex value macro exits function, so cannot be initialized here
564 .frn = {{0}}, // hex value macro exits function, so cannot be initialized here
565 .partial_key_start = {0}, // hex value macro exits function, so cannot be initialized here
568 if (opts.block < 7 || opts.block > 9) {
569 PrintAndLogEx(FAILED, "block has to be within range [7, 9], got %d", opts.block);
570 CLIParserFree(ctx);
571 return PM3_EINVARG;
574 int rnd_len = 7;
575 CLIGetHexWithReturn(ctx, 3, opts.rn.rn, &rnd_len);
577 int frnd_len = 4;
578 CLIGetHexWithReturn(ctx, 4, opts.frn.frn, &frnd_len);
580 // would prefer to use above CLIGetHexWithReturn(), but it does not
581 // appear to support optional arguments.
582 uint32_t start_key = 0;
583 int res = arg_get_u32_hexstr_def_nlen(ctx, 5, 0, &start_key, 2, true); // this stores in NATIVE ENDIAN
584 if (res == 2) {
585 PrintAndLogEx(WARNING, "start key parameter must be in range [0, FFFF]");
586 CLIParserFree(ctx);
587 return PM3_EINVARG;
589 CLIParserFree(ctx);
591 // opts structure takes value in BIG ENDIAN form
592 opts.partial_key_start[0] = (uint8_t)((start_key >> 8) & 0xFF);
593 opts.partial_key_start[1] = (uint8_t)((start_key >> 0) & 0xFF);
595 if (rnd_len != 7) {
596 PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
597 return PM3_EINVARG;
600 if (frnd_len != 4) {
601 PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frnd_len);
602 return PM3_EINVARG;
605 // Client command line parsing and validation complete ... now use the helper function
606 PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to exit");
607 em4x70_cmd_output_brute_t data;
608 int result = brute_em4x70(&opts, &data);
609 if (result == PM3_EOPABORTED) {
610 PrintAndLogEx(DEBUG, "User aborted");
611 } else if (result == PM3_ETIMEOUT) {
612 PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
613 } else if (result == PM3_SUCCESS) {
614 PrintAndLogEx(INFO, "Partial Key Response... %02X %02X", data.partial_key[0], data.partial_key[1]);
615 } else {
616 PrintAndLogEx(FAILED, "Bruteforce of partial key ( " _RED_("fail") " )");
618 return result;
621 static int CmdEM4x70Unlock(const char *Cmd) {
623 // send pin code to device, unlocking it for writing
624 CLIParserContext *ctx;
625 CLIParserInit(&ctx, "lf em 4x70 unlock",
626 "Unlock EM4x70 by sending PIN\n"
627 "Default pin may be:\n"
628 " AAAAAAAA\n"
629 " 00000000\n",
630 "lf em 4x70 unlock -p 11223344 -> Unlock with PIN\n"
631 "lf em 4x70 unlock -p 11223344 --par -> Unlock with PIN using parity commands\n"
633 void *argtable[] = {
634 arg_param_begin,
635 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
636 arg_str1("p", "pin", "<hex>", "pin, 4 bytes"),
637 arg_param_end
640 CLIExecWithReturn(ctx, Cmd, argtable, true);
642 em4x70_cmd_input_unlock_t opts = {
643 .use_parity = arg_get_lit(ctx, 1),
644 .pin = {0}, // hex value macro exits function, so cannot be initialized here
646 int pin_len = 0;
647 CLIGetHexWithReturn(ctx, 2, opts.pin, &pin_len);
648 CLIParserFree(ctx);
650 if (pin_len != 4) {
651 PrintAndLogEx(FAILED, "PIN length must be 4 bytes, got %d", pin_len);
652 return PM3_EINVARG;
655 // Client command line parsing and validation complete ... now use the helper function
656 em4x70_tag_info_t info;
657 int result = unlock_em4x70(&opts, &info);
659 if (result == PM3_ETIMEOUT) {
660 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
661 } else if (result == PM3_SUCCESS) {
662 em4x70_print_info_result(&info);
663 } else {
664 PrintAndLogEx(FAILED, "Unlocking tag ( " _RED_("fail") " )");
666 return result;
669 static int CmdEM4x70Auth(const char *Cmd) {
671 // Authenticate transponder
672 // Send 56-bit random number + pre-computed f(rnd, k) to transponder.
673 // Transponder will respond with a response
674 CLIParserContext *ctx;
676 CLIParserInit(&ctx, "lf em 4x70 auth",
677 "Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n"
678 " If F(RN) is incorrect based on the tag key, the tag will not respond\n"
679 " If F(RN) is correct based on the tag key, the tag will give a 20-bit response\n",
680 "lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> (using pm3 test key)\n"
681 "lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0 --> (using research paper key)\n"
682 "lf em 4x70 auth --rnd 7D5167003571F8 --frn 982DBCC0 --> (autorecovery test key)\n"
685 void *argtable[] = {
686 arg_param_begin,
687 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
688 arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
689 arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
690 arg_param_end
693 CLIExecWithReturn(ctx, Cmd, argtable, true);
695 em4x70_cmd_input_auth_t opts = {
696 .use_parity = arg_get_lit(ctx, 1),
697 .rn = {{0}}, // hex value macro exits function, so cannot be initialized here
698 .frn = {{0}}, // hex value macro exits function, so cannot be initialized here
700 int rn_len = 7;
701 CLIGetHexWithReturn(ctx, 2, opts.rn.rn, &rn_len);
703 int frn_len = 4;
704 CLIGetHexWithReturn(ctx, 3, opts.frn.frn, &frn_len);
705 CLIParserFree(ctx);
706 if (rn_len != 7) {
707 PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rn_len);
708 return PM3_EINVARG;
710 if (frn_len != 4) {
711 PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
712 return PM3_EINVARG;
715 // Client command line parsing and validation complete ... now use the helper function
716 em4x70_cmd_output_auth_t data;
717 int result = auth_em4x70(&opts, &data);
719 if (PM3_SUCCESS == result) {
720 PrintAndLogEx(INFO, "Tag Auth Response: %02X %02X %02X", data.grn.grn[0], data.grn.grn[1], data.grn.grn[2]);
721 } else if (PM3_ETIMEOUT == result) {
722 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
723 } else {
724 PrintAndLogEx(FAILED, "TAG Authentication ( " _RED_("fail") " )");
726 return result;
729 static int CmdEM4x70SetPIN(const char *Cmd) {
730 CLIParserContext *ctx;
731 CLIParserInit(&ctx, "lf em 4x70 setpin",
732 "Write new PIN\n",
733 "lf em 4x70 setpin -p 11223344 -> Write new PIN\n"
734 "lf em 4x70 setpin -p 11223344 --par -> Write new PIN using parity commands\n"
736 void *argtable[] = {
737 arg_param_begin,
738 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
739 arg_str1("p", "pin", "<hex>", "pin, 4 bytes"),
740 arg_param_end
742 CLIExecWithReturn(ctx, Cmd, argtable, true);
744 em4x70_cmd_input_setpin_t opts = {
745 .use_parity = arg_get_lit(ctx, 1),
746 .pin = {0}, // hex value macro exits function, so cannot be initialized here
749 int pin_len = 0;
750 CLIGetHexWithReturn(ctx, 2, opts.pin, &pin_len);
751 CLIParserFree(ctx);
753 if (pin_len != 4) {
754 PrintAndLogEx(FAILED, "PIN length must be 4 bytes, got %d", pin_len);
755 return PM3_EINVARG;
758 // Client command line parsing and validation complete ... now use the helper function
759 em4x70_tag_info_t info;
761 int result = setpin_em4x70(&opts, &info);
763 if (result == PM3_ETIMEOUT) {
764 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
765 } else if (result == PM3_SUCCESS) {
766 em4x70_print_info_result(&info);
767 PrintAndLogEx(INFO, "Writing new PIN ( " _GREEN_("ok") " )");
768 } else {
769 PrintAndLogEx(FAILED, "Writing new PIN ( " _RED_("fail") " )");
771 return result;
774 static int CmdEM4x70SetKey(const char *Cmd) {
775 CLIParserContext *ctx;
776 CLIParserInit(&ctx, "lf em 4x70 setkey",
777 "Write new 96-bit key to tag\n",
778 "lf em 4x70 setkey -k F32AA98CF5BE4ADFA6D3480B (pm3 test key)\n"
779 "lf em 4x70 setkey -k A090A0A02080000000000000 (research paper key)\n"
780 "lf em 4x70 setkey -k 022A028C02BE000102030405 (autorecovery test key)\n"
783 void *argtable[] = {
784 arg_param_begin,
785 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
786 arg_str1("k", "key", "<hex>", "Key as 12 hex bytes"),
787 arg_param_end
790 CLIExecWithReturn(ctx, Cmd, argtable, true);
792 em4x70_cmd_input_setkey_t opts = {
793 .use_parity = arg_get_lit(ctx, 1),
794 .key = {{0}}, // hex value macro exits function, so cannot be initialized here
796 int key_len = 12;
797 CLIGetHexWithReturn(ctx, 2, opts.key.k, &key_len);
798 CLIParserFree(ctx);
799 if (key_len != 12) {
800 PrintAndLogEx(FAILED, "Key length must be 12 bytes, got %d", key_len);
801 return PM3_EINVARG;
804 // Client command line parsing and validation complete ... now use the helper function
805 int result = setkey_em4x70(&opts);
807 if (PM3_ETIMEOUT == result) {
808 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
809 return PM3_ETIMEOUT;
810 } else if (PM3_SUCCESS != result) {
811 PrintAndLogEx(FAILED, "Writing new key " _RED_("fail"));
812 return result;
815 PrintAndLogEx(INFO, "Writing new key ( " _GREEN_("ok") " )");
817 // Now verify authentication using the new key, to ensure it was correctly written
818 em4x70_cmd_input_verify_auth_t opts_v = {
819 .use_parity = opts.use_parity,
820 //.rn = opts_auth.rn,
821 //.frn = opts_auth.frn,
822 //.grn = {{0}},
824 fill_buffer_prng_bytes(&opts_v.rn, sizeof(ID48LIB_NONCE));
825 id48lib_generator(&opts.key, &opts_v.rn, &opts_v.frn, &opts_v.grn);
827 // dump the auth command to the screen, to enable the user to manually check validity
828 PrintAndLogEx(INFO,
829 "Verifying auth for new key: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
830 " --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
831 " --> %02X%02X%02X",
832 opts.key.k[ 0], opts.key.k[ 1], opts.key.k[ 2], opts.key.k[ 3], opts.key.k[ 4], opts.key.k[ 5],
833 opts.key.k[ 6], opts.key.k[ 7], opts.key.k[ 8], opts.key.k[ 9], opts.key.k[10], opts.key.k[11],
834 opts_v.rn.rn[0],
835 opts_v.rn.rn[1],
836 opts_v.rn.rn[2],
837 opts_v.rn.rn[3],
838 opts_v.rn.rn[4],
839 opts_v.rn.rn[5],
840 opts_v.rn.rn[6],
841 opts_v.frn.frn[0],
842 opts_v.frn.frn[1],
843 opts_v.frn.frn[2],
844 opts_v.frn.frn[3],
845 opts_v.grn.grn[0],
846 opts_v.grn.grn[1],
847 opts_v.grn.grn[2]
850 result = verify_auth_em4x70(&opts_v);
852 if (PM3_ETIMEOUT == result) {
853 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
854 return result;
855 } else if (PM3_SUCCESS != result) {
856 PrintAndLogEx(FAILED, "Authenticating with new key ( " _RED_("fail") " )");
857 return result;
858 } else {
859 PrintAndLogEx(INFO, "Authenticating with new key ( " _GREEN_("ok") " )");
861 return result;
864 typedef struct _em4x70_recovery_data_t {
866 em4x70_cmd_input_recover_t opts;
867 em4x70_cmd_output_recover_t data;
869 uint8_t keys_validated_count;
870 ID48LIB_NONCE alt_nonce;
871 ID48LIB_FRN alt_frn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
872 ID48LIB_GRN alt_grn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
873 bool potential_keys_validated[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
874 } em4x70_recovery_data_t;
876 static int CmdEM4x70Recover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_t *out_results) {
878 memset(out_results, 0, sizeof(em4x70_cmd_input_recover_t));
880 int result = PM3_SUCCESS;
882 CLIParserContext *ctx;
883 CLIParserInit(
884 &ctx,
885 "lf em 4x70 recover",
886 "After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover\n"
887 "key bits 47..00. By default, this process does NOT require a tag to be present.\n"
888 "\n"
889 "By default, the potential keys are shown (typically 1-6) along with a corresponding\n"
890 "'lf em 4x70 auth' command that will authenticate, if that potential key is correct.\n"
891 "The user can copy/paste these commands when the tag is present to manually check\n"
892 "which of the potential keys is correct.\n"
893 // "\n"
894 // "If the `--verify` option is provided, the tag must be present. The rnd/frn parameters will\n"
895 // "be used to authenticate against the tag, and then any potential keys will be automatically\n"
896 // "be checked for correctness against the tag, reducing manual steps.\n"
898 "lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
899 "lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
900 "lf em 4x70 recover --key 022A028C02BE --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)\n"
903 void *argtable[] = {
904 arg_param_begin,
905 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
906 arg_str1("k", "key", "<hex>", "Key as 6 hex bytes"),
907 arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
908 arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
909 arg_str1(NULL, "grn", "<hex>", "G(RN) 20-bit as 3 hex bytes"),
910 //arg_lit0(NULL, "verify", "automatically use tag for validation"),
911 arg_param_end
914 // do the command line arguments even parse?
915 if (CLIParserParseString(ctx, Cmd, argtable, arg_getsize(argtable), true)) {
916 result = PM3_ESOFT;
919 int key_len = 0; // must be 6 bytes hex data
920 int rnd_len = 0; // must be 7 bytes hex data
921 int frn_len = 0; // must be 4 bytes hex data
922 int grn_len = 0; // must be 3 bytes hex data
924 // if all OK so far, convert to internal data structure
925 if (PM3_SUCCESS == result) {
926 // magic number == index in argtable above. Fragile technique!
927 out_results->parity = arg_get_lit(ctx, 1);
928 if (CLIParamHexToBuf(arg_get_str(ctx, 2), &(out_results->key.k[0]), 12, &key_len)) {
929 result = PM3_ESOFT;
931 if (CLIParamHexToBuf(arg_get_str(ctx, 3), &(out_results->nonce.rn[0]), 7, &rnd_len)) {
932 result = PM3_ESOFT;
934 if (CLIParamHexToBuf(arg_get_str(ctx, 4), &(out_results->frn.frn[0]), 4, &frn_len)) {
935 result = PM3_ESOFT;
937 if (CLIParamHexToBuf(arg_get_str(ctx, 5), &(out_results->grn.grn[0]), 3, &grn_len)) {
938 result = PM3_ESOFT;
940 //out_results->verify = arg_get_lit(ctx, 6);
943 // if all OK so far, do additional parameter validation
944 if (PM3_SUCCESS == result) {
945 // Validate number of bytes read for hex data
946 if (key_len != 6) {
947 PrintAndLogEx(FAILED, "Key length must be 6 bytes, got %d", key_len);
948 result = PM3_EINVARG;
951 if (rnd_len != 7) {
952 PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
953 result = PM3_EINVARG;
956 if (frn_len != 4) {
957 PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
958 result = PM3_EINVARG;
961 if (grn_len != 3) {
962 PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes, got %d", grn_len);
963 result = PM3_EINVARG;
967 // single exit point
968 CLIParserFree(ctx);
969 return result;
972 static int CmdEM4x70Recover(const char *Cmd) {
973 // From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
974 // Partial Key-Update Attack -- final 48 bits (after optimized version gets k95..k48)
975 em4x70_recovery_data_t recover_ctx = {0};
976 int result = PM3_SUCCESS;
978 result = CmdEM4x70Recover_ParseArgs(Cmd, &recover_ctx.opts);
979 // recover the potential keys -- no more than a few seconds
980 if (PM3_SUCCESS == result) {
982 result = recover_em4x70(&recover_ctx.opts, &recover_ctx.data);
983 if (PM3_EOVFLOW == result) {
984 PrintAndLogEx(ERR, "Found more than %d potential keys. This is unexpected and likely a code failure.", MAXIMUM_ID48_RECOVERED_KEY_COUNT);
985 } else if (PM3_SUCCESS != result) {
986 PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
990 // generate alternate authentication for each potential key -- no error paths, sub-second execution
991 if (PM3_SUCCESS == result) {
993 fill_buffer_prng_bytes(&recover_ctx.alt_nonce, sizeof(ID48LIB_NONCE));
994 for (uint8_t i = 0; i < recover_ctx.data.potential_key_count; ++i) {
995 // generate the alternate frn/grn for the alternate nonce
996 id48lib_generator(&recover_ctx.data.potential_keys[i], &recover_ctx.alt_nonce, &recover_ctx.alt_frn[i], &recover_ctx.alt_grn[i]);
1000 // display alternate authentication for each potential key -- no error paths
1001 if (PM3_SUCCESS == result) {
1003 PrintAndLogEx(INFO, "Recovered %d potential keys:", recover_ctx.data.potential_key_count);
1004 for (uint8_t i = 0; i < recover_ctx.data.potential_key_count; ++i) {
1005 // generate an alternative authentication based on the potential key
1006 // and the alternate nonce.
1007 ID48LIB_KEY q = recover_ctx.data.potential_keys[i];
1008 ID48LIB_FRN alt_frn = recover_ctx.alt_frn[i];
1009 ID48LIB_GRN alt_grn = recover_ctx.alt_grn[i];
1011 // dump the results to screen, to enable the user to manually check validity
1012 PrintAndLogEx(INFO,
1013 "Potential Key #%d: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
1014 " --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
1015 " --> %02X%02X%02X",
1017 q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
1018 q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11],
1019 recover_ctx.alt_nonce.rn[0],
1020 recover_ctx.alt_nonce.rn[1],
1021 recover_ctx.alt_nonce.rn[2],
1022 recover_ctx.alt_nonce.rn[3],
1023 recover_ctx.alt_nonce.rn[4],
1024 recover_ctx.alt_nonce.rn[5],
1025 recover_ctx.alt_nonce.rn[6],
1026 alt_frn.frn[0],
1027 alt_frn.frn[1],
1028 alt_frn.frn[2],
1029 alt_frn.frn[3],
1030 alt_grn.grn[0],
1031 alt_grn.grn[1],
1032 alt_grn.grn[2]
1035 printf("\n");
1037 // which of those keys actually validates?
1038 if (PM3_SUCCESS == result && recover_ctx.opts.verify) {
1039 // TODO: automatic verification against a present tag.
1040 // Updates ctx.potential_keys_validated[10] and ctx.keys_validated_count
1041 PrintAndLogEx(WARNING, "Automatic verification against tag is not yet implemented.");
1042 // 0. verify a tag is present
1043 // 1. verify the parameters provided authenticate against the tag
1044 // if not, print "Authentication failed. Verify the current tag matches parameters provided."
1045 // print the authentication command used (allows user to easily copy/paste)
1046 // SET ERROR
1047 // 2. for each potential key:
1048 // a. Attempt to authentic against the tag using alt_nonce and alt_frn[i]
1049 // b. verify tag's response is alt_grn[i]
1050 // c. if successful, set ctx.potential_keys_validated[i] = true and increment ctx.keys_validated_count
1052 // All validation done... now just interpret the results....
1054 // 3. if ctx.keys_validated_count == 0, print "No keys recovered. Check tag for good coupling (position, etc)?"
1055 // 4. if ctx.keys_validated_count >= 2, print "Multiple keys recovered. Run command again (will use different alt nonce)?"
1056 // 5. if ctx.keys_validated_count == 1, print "Found key: " ...
1058 return result;
1061 static int CmdEM4x70AutoRecover_ParseArgs(const char *Cmd, em4x70_cmd_input_recover_t *out_results) {
1062 memset(out_results, 0, sizeof(em4x70_cmd_input_recover_t));
1064 int result = PM3_SUCCESS;
1065 // The following key is found quickly, and has multiple potential keys.
1066 // Useful for quicker testing, as this function could take over 2 hours.
1067 // lf em 4x70 setkey -k 001200340055BAADCAFEF00D
1068 // lf em 4x70 autorecover --rnd 1782779E7E3BC8 --frn 00357080 --grn F3C480
1069 CLIParserContext *ctx;
1070 CLIParserInit(
1071 &ctx,
1072 "lf em 4x70 autorecover",
1073 "This command will perform automatic recovery of the key from a writable tag.\n"
1074 "All steps are possible to do manually. The corresponding sequence, if done\n"
1075 "manually, is as follows:\n"
1076 "1. Verify passed parameters authenticate with the tag (safety check)\n"
1077 " " _YELLOW_("lf em 4x70 auth --rnd <rnd_1> --frn <frn_1>") "\n"
1078 "2. Brute force the key bits in block 9\n"
1079 " " _YELLOW_("lf em 4x70 write -b 9 -d 0000") "\n"
1080 " " _YELLOW_("lf em 4x70 recover -b 9 --rnd <rnd_1> --frn <frn_1>") "\n"
1081 " " _YELLOW_("lf em 4x70 write -b 9 -d <key_block_9>") "\n"
1082 "3. Brute force the key bits in block 8\n"
1083 " " _YELLOW_("lf em 4x70 write -b 8 -d 0000") "\n"
1084 " " _YELLOW_("lf em 4x70 recover -b 8 --rnd <rnd_1> --frn <frn_1>") "\n"
1085 " " _YELLOW_("lf em 4x70 write -b 8 -d <key_block_8>") "\n"
1086 "4. Brute force the key bits in block 7\n"
1087 " " _YELLOW_("lf em 4x70 write -b 7 -d 0000)") "\n"
1088 " " _YELLOW_("lf em 4x70 recover -b 7 --rnd <rnd_1> --frn <frn_1>") "\n"
1089 " " _YELLOW_("lf em 4x70 write -b 7 -d <key_block_7>") "\n"
1090 "5. Recover potential values of the lower 48 bits of the key\n"
1091 " " _YELLOW_("lf em 4x70 recover --key <key_block_9><key_block_8><key_block_7> --rnd <rnd_1> --frn <frn_1>") "\n"
1092 "6. Verify which potential key is actually on the tag (using a different rnd/frn combination)\n"
1093 " " _YELLOW_("lf em 4x70 auth --rnd <rnd_2> --frn <frn_N>") "\n"
1094 "7. Print the validated key\n"
1095 "\n"
1096 "This command simply requires the rnd/frn/grn from a single known-good authentication.\n"
1097 "\n"
1099 // "\n"
1100 // "If the `--verify` option is provided, the tag must be present. The rnd/frn parameters will\n"
1101 // "be used to authenticate against the tag, and then any potential keys will be automatically\n"
1102 // "be checked for correctness against the tag, reducing manual steps.\n"
1104 "lf em 4x70 autorecover --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
1105 "lf em 4x70 autorecover --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
1106 "lf em 4x70 autorecover --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)\n"
1109 void *argtable[] = {
1110 arg_param_begin,
1111 arg_lit0(NULL, "par", "Add parity bit when sending commands"),
1112 arg_str1(NULL, "rnd", "<hex>", "Random 56-bit from known-good authentication"),
1113 arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes from known-good authentication"),
1114 arg_str1(NULL, "grn", "<hex>", "G(RN) 20-bit as 3 hex bytes from known-good authentication"),
1115 //arg_lit0(NULL, "verify", "automatically use tag for validation"),
1116 arg_param_end
1119 CLIExecWithReturn(ctx, Cmd, argtable, true);
1121 int rnd_len = 0; // must be 7 bytes hex data
1122 int frn_len = 0; // must be 4 bytes hex data
1123 int grn_len = 0; // must be 3 bytes hex data
1124 out_results->parity = arg_get_lit(ctx, 1);
1125 CLIGetHexWithReturn(ctx, 2, out_results->nonce.rn, &rnd_len);
1126 CLIGetHexWithReturn(ctx, 3, out_results->frn.frn, &frn_len);
1127 CLIGetHexWithReturn(ctx, 4, out_results->grn.grn, &grn_len);
1128 CLIParserFree(ctx);
1130 if (rnd_len != 7) {
1131 PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
1132 result = PM3_EINVARG;
1135 if (frn_len != 4) {
1136 PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes, got %d", frn_len);
1137 result = PM3_EINVARG;
1140 if (grn_len != 3) {
1141 PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes, got %d", grn_len);
1142 result = PM3_EINVARG;
1144 return result;
1147 static int CmdEM4x70AutoRecover(const char *Cmd) {
1148 em4x70_cmd_input_recover_t opts = {0};
1149 em4x70_cmd_output_recover_t data = {0};
1150 em4x70_tag_info_t tag_info = {0};
1152 int result = CmdEM4x70AutoRecover_ParseArgs(Cmd, &opts);
1153 // 0. Parse the command line
1154 if (PM3_SUCCESS != result) {
1155 return result;
1158 // The parameters are valid. Per Iceman's direct request, the code has been updated
1159 // to immediately exit on errors. Unfortunately, this requirement limits the clarity
1160 // of summarizing the failure (and providing options for recovery in case of failures)
1161 // at a single point at the end of the function. It will also undoubtedly reduce
1162 // code coverage numbers, when those are tracked.
1164 // As to clarity, if failures occurred in steps 2-4, it was expected that the cleanup
1165 // code would, in a single location, verify if the original authentication worked.
1166 // If so, then the tag was left in a good state (even if an error occurred).
1167 // If not, then at least it would be possible for the user to restart manually, and
1168 // to be given clear instructions on how to do that to return the tag to its original
1169 // state.
1171 // TODO: Wrap this entire function in another function, whose sole purpose is to
1172 // perform that additional cleanup? Not a great solution. Pity, as the
1173 // cleanup code was much more helpful than the below print statements.
1174 int last_successful_step = 0;
1175 char rnd_string[14 + 1] = {0};
1176 char frn_string[ 8 + 1] = {0};
1177 char grn_string[ 6 + 1] = {0};
1178 // These strings will be re-used often, are safe to pre-allocate, and make later PrintAndLogEx() calls cleaner.
1179 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]);
1180 snprintf(frn_string, 9, "%02X%02X%02X%02X", opts.frn.frn[0], opts.frn.frn[1], opts.frn.frn[2], opts.frn.frn[3]);
1181 snprintf(grn_string, 7, "%02X%02X%02X", opts.grn.grn[0], opts.grn.grn[1], opts.grn.grn[2]);
1183 // 1. Verify passed parameters authenticate with the tag (safety check)
1184 // lf em 4x70 auth --rnd <rnd_1> --frn <frn_1>
1185 if (PM3_SUCCESS == result) {
1186 PrintAndLogEx(INFO, "Step 1. Verifying passed parameters authenticate with the tag (safety check)");
1187 PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 auth --rnd %s --frn %s"), rnd_string, frn_string);
1189 em4x70_cmd_input_auth_t opts_auth = {
1190 .use_parity = opts.parity,
1191 .rn = opts.nonce,
1192 .frn = opts.frn,
1195 em4x70_cmd_output_auth_t tag_grn;
1197 result = auth_em4x70(&opts_auth, &tag_grn);
1199 if (PM3_ETIMEOUT == result) {
1200 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
1201 return result;
1202 } else if (PM3_SUCCESS != result) {
1203 PrintAndLogEx(FAILED, "Authenticating with provided values ( " _RED_("fail") " )");
1204 return result;
1205 } else if (memcmp(&opts.grn, &tag_grn, sizeof(ID48LIB_GRN)) != 0) {
1206 PrintAndLogEx(FAILED, "Authenticating with new key returned %02x %02x %02x"
1207 , tag_grn.grn.grn[0]
1208 , tag_grn.grn.grn[1]
1209 , tag_grn.grn.grn[2]
1211 PrintAndLogEx(FAILED, "Expected %s [maybe 5 lsb of key wrong?] ( " _RED_("fail") " )", grn_string);
1212 result = PM3_EWRONGANSWER;
1213 return result;
1215 last_successful_step = 1;
1218 // 2/3/4. Brute force the key bits in block 7,8,9
1219 // lf em 4x70 write -b N -d 0000
1220 // lf em 4x70 brute -b N --rnd <rnd_1> --frn <frn_1>
1221 // lf em 4x70 write -b N -d <key_block_N>
1222 for (uint8_t block = 9; (PM3_SUCCESS == result) && (block > 6); --block) {
1223 uint8_t step =
1224 block == 9 ? 2 :
1225 block == 8 ? 3 :
1226 block == 7 ? 4 :
1227 197;
1228 em4x70_cmd_output_brute_t brute = {0};
1230 // lf em 4x70 write -b N -d 0000
1231 if (PM3_SUCCESS == result) {
1232 PrintAndLogEx(INFO, "Step %d. Brute force the key bits in block %d", step, block);
1233 PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 write -b %d -d 0000"), block);
1235 em4x70_cmd_input_writeblock_t opt_write_zeros = {
1236 .use_parity = opts.parity,
1237 .block = block,
1238 .value = {0x00, 0x00},
1241 result = writeblock_em4x70(&opt_write_zeros, &tag_info);
1243 if (PM3_ETIMEOUT == result) {
1244 PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
1245 PrintAndLogEx(HINT, "Block %d data may have been overwritten. Manually restart at step %d", block, step);
1246 return result;
1247 } else if (PM3_SUCCESS != result) {
1248 PrintAndLogEx(FAILED, "Writing block %d ( " _RED_("fail") " )", block);
1249 PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d", block, step);
1250 return result;
1254 // lf em 4x70 brute -b N --rnd <rnd_1> --frn <frn_1>
1255 if (PM3_SUCCESS == result) {
1256 PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 brute -b %d --rnd %s --frn %s"), block, rnd_string, frn_string);
1258 em4x70_cmd_input_brute_t opts_brute = {
1259 .use_parity = opts.parity,
1260 .block = block,
1261 .rn = opts.nonce,
1262 .frn = opts.frn,
1263 .partial_key_start = {0},
1266 result = brute_em4x70(&opts_brute, &brute);
1268 if (PM3_ETIMEOUT == result) {
1269 PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
1270 PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d", block, step);
1271 return result;
1272 } else if (PM3_SUCCESS != result) {
1273 PrintAndLogEx(FAILED, "Writing block %d ( " _RED_("fail") " )", block);
1274 PrintAndLogEx(HINT, "Block %d data was overwritten. Manually restart at step %d", block, step);
1275 return result;
1276 } else {
1277 PrintAndLogEx(INFO, " Found: Partial key in block %d is " _GREEN_("%02X%02X")
1278 , block
1279 , brute.partial_key[0]
1280 , brute.partial_key[1]
1282 // Save the partial key...
1283 if (block == 9) {
1284 opts.key.k[0] = brute.partial_key[0];
1285 opts.key.k[1] = brute.partial_key[1];
1286 } else if (block == 8) {
1287 opts.key.k[2] = brute.partial_key[0];
1288 opts.key.k[3] = brute.partial_key[1];
1289 } else if (block == 7) {
1290 opts.key.k[4] = brute.partial_key[0];
1291 opts.key.k[5] = brute.partial_key[1];
1295 // lf em 4x70 write -b N -d <key_block_N>
1296 if (PM3_SUCCESS == result) {
1297 PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 write -b %d -d %02X%02X"), block, brute.partial_key[0], brute.partial_key[1]);
1299 em4x70_cmd_input_writeblock_t opt_write_zeros = {
1300 .use_parity = opts.parity,
1301 .block = block,
1302 .value = {brute.partial_key[0], brute.partial_key[1]},
1305 result = writeblock_em4x70(&opt_write_zeros, &tag_info);
1307 if (PM3_ETIMEOUT == result) {
1308 PrintAndLogEx(FAILED, "Timeout while waiting for reply.");
1309 PrintAndLogEx(HINT, "Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten", block, brute.partial_key[0], brute.partial_key[1]);
1310 return result;
1311 } else if (PM3_SUCCESS != result) {
1312 PrintAndLogEx(FAILED, "Writing block %d ( " _RED_("fail") " )", block);
1313 PrintAndLogEx(HINT, "Block %d data (" _GREEN_("%02X%02X") ") may need to be rewritten", block, brute.partial_key[0], brute.partial_key[1]);
1314 return result;
1318 if (PM3_SUCCESS == result) {
1319 last_successful_step = step;
1322 // The good news is that, if the above succeeded, then from this point forward, the tag remains in a known-good state.
1324 char key_string[24 + 1] = {0}; // holds partial key initially, full key later
1325 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]);
1327 // 5. Recover potential values of the lower 48 bits of the key
1328 // lf em 4x70 recover --key <key_block_9><key_block_8><key_block_7> --rnd <rnd_1> --frn <frn_1>
1329 if (PM3_SUCCESS == result) {
1330 PrintAndLogEx(INFO, "Step 5. Recover potential values of the lower 48 bits of the key");
1331 PrintAndLogEx(HINT, " " _YELLOW_("lf em 4x70 recover --key %s --rnd %s --frn %s --grn %s"), key_string, rnd_string, frn_string, grn_string);
1333 result = recover_em4x70(&opts, &data);
1335 if (PM3_EOVFLOW == result) {
1336 PrintAndLogEx(ERR, "Found more than %d potential keys. This is unexpected and likely a code failure.", MAXIMUM_ID48_RECOVERED_KEY_COUNT);
1337 return result;
1338 } else if (PM3_SUCCESS != result) {
1339 PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
1340 return result;
1341 } else {
1342 PrintAndLogEx(INFO, " Found " _GREEN_("%d") " potential keys", data.potential_key_count);
1343 for (uint8_t idx = 0; idx < data.potential_key_count; ++idx) {
1344 ID48LIB_KEY q = data.potential_keys[idx];
1345 PrintAndLogEx(DEBUG, " Potential Key %d: %s %02X%02X%02X%02X%02X%02X"
1346 , idx
1347 , key_string
1348 , q.k[ 6]
1349 , q.k[ 7]
1350 , q.k[ 8]
1351 , q.k[ 9]
1352 , q.k[10]
1353 , q.k[11]
1356 last_successful_step = 5;
1360 // 6. Verify which potential key is actually on the tag (using a different rnd/frn combination)
1361 // lf em 4x70 auth --rnd <rnd_2> --frn <frn_N>
1362 if (PM3_SUCCESS == result) {
1363 PrintAndLogEx(INFO, "Step 6. Verify which potential key is actually on the tag");
1365 em4x70_cmd_input_verify_auth_t opts_v = {
1366 .use_parity = opts.parity,
1367 //.rn = {{0}},
1368 //.frn = {{0}},
1369 //.grn = {{0}},
1372 // TODO: retry a few time, if >1 key validated with the new nonce
1373 bool continue_loop = true;
1374 bool found_one_key = false;
1375 bool found_more_than_one_key = false;
1376 uint8_t first_validated_key_idx = 0xFF;
1378 for (uint8_t attempt = 0; continue_loop && (attempt < 10); ++attempt) {
1379 continue_loop = false;
1380 found_one_key = false;
1381 found_more_than_one_key = false;
1382 first_validated_key_idx = 0xFF;
1383 fill_buffer_prng_bytes(&opts_v.rn, sizeof(ID48LIB_NONCE));
1385 for (uint8_t i = 0; i < data.potential_key_count; ++i) {
1386 // generate the alternate frn/grn for this key + nonce combo
1387 id48lib_generator(&data.potential_keys[i], &opts_v.rn, &opts_v.frn, &opts_v.grn);
1389 int tmpResult = verify_auth_em4x70(&opts_v);
1390 if (PM3_SUCCESS == tmpResult) {
1391 if (!found_one_key) {
1392 first_validated_key_idx = i;
1393 found_one_key = true;
1394 } else {
1395 found_more_than_one_key = true;
1400 if (found_one_key == false) {
1401 PrintAndLogEx(WARNING, "No potential keys validated. Will try again with different nonce");
1402 continue_loop = true;
1403 msleep(2000); // delay 2 seconds ... in case tag was bumped, etc.
1404 } else if (found_more_than_one_key) {
1405 PrintAndLogEx(WARNING, "Multiple potential keys validated. Will try different nonce");
1406 continue_loop = true;
1407 msleep(2000); // delay 2 seconds ... in case tag was bumped, etc.
1408 } else {
1409 last_successful_step = 6;
1413 if ((found_one_key == false) || found_more_than_one_key) {
1414 PrintAndLogEx(FAILED, "Unable to recover any of the multiple potential keys");
1415 PrintAndLogEx(FAILED, "Check tag for good coupling / position!");
1416 return PM3_EFAILED;
1417 } else {
1418 // print the validated key to the string buffer (for step 7)
1419 ID48LIB_KEY q = data.potential_keys[first_validated_key_idx];
1420 snprintf(key_string, 25, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
1421 q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
1422 q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11]
1426 // 7. Print the validated key
1427 if (PM3_SUCCESS == result) {
1428 PrintAndLogEx(SUCCESS, "Recovered key... " _GREEN_("%s"), key_string);
1429 last_successful_step = 7;
1432 // For posterity, step 7 used to do the following:
1433 // 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
1434 // If success ... print the final key
1435 // Else if authentication works with original rnd/frn ... print
1436 // Else warn user that the tag is no longer in original state, and print steps to return it to a good state.
1437 (void)last_successful_step;
1438 return result;
1441 static int CmdEM4x70Calc_ParseArgs(const char *Cmd, em4x70_cmd_input_calculate_t *out_results) {
1443 memset(out_results, 0, sizeof(em4x70_cmd_input_calculate_t));
1445 int result = PM3_SUCCESS;
1447 CLIParserContext *ctx;
1448 CLIParserInit(
1449 &ctx,
1450 "lf em 4x70 calc",
1451 "Calculates both the reader and tag challenge for a user-provided key and rnd.\n"
1453 "lf em 4x70 calc --key F32AA98CF5BE4ADFA6D3480B --rnd 45F54ADA252AAC (pm3 test key)\n" // --frn 4866BB70 --grn 9BD180
1454 "lf em 4x70 calc --key A090A0A02080000000000000 --rnd 3FFE1FB6CC513F (research paper key)\n" // --frn F355F1A0 --grn 609D60
1455 "lf em 4x70 calc --key 022A028C02BE000102030405 --rnd 7D5167003571F8 (autorecovery test key)\n" // --frn 982DBCC0 --grn 36C0E0
1457 void *argtable[] = {
1458 arg_param_begin,
1459 arg_str1(NULL, "key", "<hex>", "Key 96-bit as 12 hex bytes"),
1460 arg_str1(NULL, "rnd", "<hex>", "56-bit random value sent to tag for authentication"),
1461 arg_param_end
1463 CLIExecWithReturn(ctx, Cmd, argtable, true);
1464 int key_len = 0; // must be 12 bytes hex data
1465 int rnd_len = 0; // must be 7 bytes hex data
1467 // These macros hide early function return on error ... including free'ing ctx.
1468 CLIGetHexWithReturn(ctx, 1, out_results->key.k, &key_len);
1469 CLIGetHexWithReturn(ctx, 2, out_results->rn.rn, &rnd_len);
1470 CLIParserFree(ctx);
1472 if (key_len != 12) {
1473 PrintAndLogEx(FAILED, "Key length must be 12 bytes, got %d", key_len);
1474 result = PM3_EINVARG;
1477 if (rnd_len != 7) {
1478 PrintAndLogEx(FAILED, "Random number length must be 7 bytes, got %d", rnd_len);
1479 result = PM3_EINVARG;
1481 return result;
1484 static int CmdEM4x70Calc(const char *Cmd) {
1485 em4x70_cmd_input_calculate_t opts = {0};
1486 em4x70_cmd_output_calculate_t data = {0};
1488 // 0. Parse the command line
1489 int result = CmdEM4x70Calc_ParseArgs(Cmd, &opts);
1490 if (PM3_SUCCESS != result) {
1491 return result;
1494 // There are no failure paths. All inputs are valid, and ID48LIB doesn't add any failure paths.
1495 id48lib_generator(&opts.key, &opts.rn, &data.frn, &data.grn);
1497 char key_string[24 + 1] = {0};
1498 char rnd_string[14 + 1] = {0};
1499 char frn_string[ 8 + 1] = {0};
1500 char grn_string[ 6 + 1] = {0};
1501 if (true) {
1502 snprintf(
1503 key_string, 25,
1504 "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
1505 opts.key.k[ 0], opts.key.k[ 1], opts.key.k[ 2], opts.key.k[ 3],
1506 opts.key.k[ 4], opts.key.k[ 5], opts.key.k[ 6], opts.key.k[ 7],
1507 opts.key.k[ 8], opts.key.k[ 9], opts.key.k[10], opts.key.k[11]
1509 snprintf(
1510 rnd_string, 15,
1511 "%02X%02X%02X%02X%02X%02X%02X",
1512 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]
1514 snprintf(
1515 frn_string, 9,
1516 "%02X%02X%02X%02X",
1517 data.frn.frn[0], data.frn.frn[1], data.frn.frn[2], data.frn.frn[3]
1519 snprintf(
1520 grn_string, 7,
1521 "%02X%02X%02X",
1522 data.grn.grn[0], data.grn.grn[1], data.grn.grn[2]
1525 PrintAndLogEx(SUCCESS, "KEY: %s RND: %s FRN: " _GREEN_("%s") " GRN: " _GREEN_("%s"), key_string, rnd_string, frn_string, grn_string);
1526 return PM3_SUCCESS;
1529 // Must be declared to be used in the table,
1530 // but cannot be defined yet because it uses the table.
1531 static int CmdHelp(const char *Cmd);
1533 static command_t CommandTable[] = {
1534 {"help", CmdHelp, AlwaysAvailable, "This help"},
1535 {"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
1536 {"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"},
1537 {"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"},
1538 {"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"},
1539 {"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"},
1540 {"setpin", CmdEM4x70SetPIN, IfPm3EM4x70, "Write PIN"},
1541 {"setkey", CmdEM4x70SetKey, IfPm3EM4x70, "Write key"},
1542 {"calc", CmdEM4x70Calc, AlwaysAvailable, "Calculate EM4x70 challenge and response"},
1543 {"recover", CmdEM4x70Recover, AlwaysAvailable, "Recover remaining key from partial key"},
1544 {"autorecover", CmdEM4x70AutoRecover, IfPm3EM4x70, "Recover entire key from writable tag"},
1545 {NULL, NULL, NULL, NULL}
1548 static int CmdHelp(const char *Cmd) {
1549 (void)Cmd; // Cmd is not used so far
1550 CmdsHelp(CommandTable);
1551 return PM3_SUCCESS;
1554 ///////////////////////////////////////////////////////////////////////////////
1555 // Only two functions need to be non-static:
1556 // * CmdLFEM4X70()
1557 // * detect_4x70_block()
1558 int CmdLFEM4X70(const char *Cmd) {
1559 clearCommandBuffer();
1560 return CmdsParse(CommandTable, Cmd);
1563 // used by `lf search` and `search`, this is a quick test for EM4x70 tag
1564 // In alignment with other tags implementations, this also dumps basic information
1565 // about the tag, if one is found.
1566 // Use helper function `get_em4x70_info()` if wanting to limit / avoid output.
1567 bool detect_4x70_block(void) {
1568 em4x70_tag_info_t info;
1569 em4x70_cmd_input_info_t opts = { 0 };
1571 int result = get_em4x70_info(&opts, &info);
1573 if (result == PM3_ETIMEOUT) { // consider removing this output?
1574 PrintAndLogEx(WARNING, "Timeout while waiting for reply.");
1576 return result == PM3_SUCCESS;