From de86cd85d16cc8a03814abb22e1eccb0a6528853 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 3 Sep 2024 11:43:22 +0200 Subject: [PATCH] Added support for dumping FM11RF08S data at once --- CHANGELOG.md | 3 +- armsrc/appmain.c | 2 +- armsrc/mifarecmd.c | 17 ++++++- armsrc/mifarecmd.h | 2 +- client/pyscripts/fm11rf08s_recovery.py | 70 ++++++++++++++++++++------ client/src/cmdhfmf.c | 43 ++++++++++++---- 6 files changed, 110 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28fc5060a..ff84af618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ 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 support for collecting all fm11rf08s nT/{nT}/par_err at once (@doegox) +- Added support for dumping FM11RF08S data at once (@doegox) +- Added support for collecting all FM11RF08S nT/{nT}/par_err at once (@doegox) - Fixed `hf mfu wrbl` - compatibility write only writes 4 bytes. Now handled correctly (@iceman1001) - Changed `hf mfu info` - better magic tag detection (@iceman1001) - Added ELECTRA pattern decoding in `lf search` (@CiRIP) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 4464db77a..b28ecdb51 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1751,7 +1751,7 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES: { - MifareAcquireStaticEncryptedNonces(packet->data.asBytes); + MifareAcquireStaticEncryptedNonces(packet->oldarg[0], packet->data.asBytes); break; } case CMD_HF_MIFARE_ACQ_NONCES: { diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 6897cdb49..6079e12d1 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1036,7 +1036,7 @@ void MifareAcquireEncryptedNonces(uint32_t arg0, uint32_t arg1, uint32_t flags, // acquire static encrypted nonces in order to perform the attack described in // Philippe Teuwen, "MIFARE Classic: exposing the static encrypted nonce variant" //----------------------------------------------------------------------------- -void MifareAcquireStaticEncryptedNonces(uint8_t *key) { +void MifareAcquireStaticEncryptedNonces(uint32_t flags, uint8_t *key) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs; @@ -1048,6 +1048,7 @@ void MifareAcquireStaticEncryptedNonces(uint8_t *key) { uint8_t buf[PM3_CMD_DATA_SIZE] = {0x00}; uint64_t ui64Key = bytes_to_num(key, 6); + bool with_data = flags & 1; uint32_t cuid = 0; int16_t isOK = PM3_SUCCESS; uint16_t num_nonces = 0; @@ -1108,6 +1109,20 @@ void MifareAcquireStaticEncryptedNonces(uint8_t *key) { isOK = PM3_ESOFT; goto out; }; + if (with_data) { + uint8_t data[16]; + for (uint8_t tb = blockNo; tb < blockNo + 4; tb++) { + memset(data, 0x00, sizeof(data)); + int res = mifare_classic_readblock(pcs, tb, data); + if (res == 1) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Read error"); + isOK = PM3_ESOFT; + goto out; + } + emlSetMem_xt(data, tb, 1, 16); + } + } + // nested authentication uint16_t len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType + 4, blockNo, receivedAnswer, par_enc, NULL); if (len != 4) { diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index fde47d224..86e0afd8f 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -37,7 +37,7 @@ void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8 void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key); void MifareAcquireEncryptedNonces(uint32_t arg0, uint32_t arg1, uint32_t flags, uint8_t *datain); -void MifareAcquireStaticEncryptedNonces(uint8_t *key); +void MifareAcquireStaticEncryptedNonces(uint32_t flags, uint8_t *key); void MifareAcquireNonces(uint32_t arg0, uint32_t flags); void MifareChkKeys(uint8_t *datain, uint8_t reserved_mem); void MifareChkKeys_fast(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); diff --git a/client/pyscripts/fm11rf08s_recovery.py b/client/pyscripts/fm11rf08s_recovery.py index 4ebbcf4aa..da819b36d 100755 --- a/client/pyscripts/fm11rf08s_recovery.py +++ b/client/pyscripts/fm11rf08s_recovery.py @@ -103,14 +103,37 @@ if args.init_check: print_key(sec, 1, found_keys[sec][1]) print("Getting nonces...") -cmd = f"hf mf isen --collect_fm11rf08s --key {BACKDOOR_RF08S}" +cmd = f"hf mf isen --collect_fm11rf08s_with_data --key {BACKDOOR_RF08S}" p.console(cmd) try: - nt, nt_enc, par_err = json.loads(p.grabbed_output) + nt, nt_enc, par_err, data = json.loads(p.grabbed_output) except json.decoder.JSONDecodeError: print("Error getting nonces, abort.") exit() +print("Generating first dump file") +dumpfile = f"hf-mf-{uid:08X}-dump.bin" +with (open(dumpfile, "wb")) as f: + for sec in range(NUM_SECTORS): + for b in range(4): + d = data[(sec * 4) + b] + if b == 3: + ka = found_keys[sec][0] + kb = found_keys[sec][1] + if ka == "": + ka = "FFFFFFFFFFFF" + if kb == "": + kb = "FFFFFFFFFFFF" + d = ka + d[12:20] + kb + f.write(bytes.fromhex(d)) +print(f"Data have been dumped to `{dumpfile}`") + +elapsed_time1 = time.time() - start_time +minutes = int(elapsed_time1 // 60) +seconds = int(elapsed_time1 % 60) +print("----Step 1: " + color(f"{minutes:2}", fg="yellow") + " minutes " + + color(f"{seconds:2}", fg="yellow") + " seconds -----------") + if os.path.isfile(DICT_DEF_PATH): print(f"Loading {DICT_DEF}") with open(DICT_DEF_PATH, 'r', encoding='utf-8') as file: @@ -259,11 +282,11 @@ if args.debug: print(f" {sec:03} | {sec*4+3:03} | {candidates[sec][0]:6} | {candidates[sec][1]:6} ") total_candidates = sum(candidates[sec][0] + candidates[sec][1] for sec in range(NUM_SECTORS)) -elapsed_time = time.time() - start_time -minutes1 = int(elapsed_time // 60) -seconds1 = int(elapsed_time % 60) -print("----Step 1: " + color(f"{minutes1:2}", fg="yellow") + " minutes " + - color(f"{seconds1:2}", fg="yellow") + " seconds -----------") +elapsed_time2 = time.time() - start_time - elapsed_time1 +minutes = int(elapsed_time2 // 60) +seconds = int(elapsed_time2 % 60) +print("----Step 2: " + color(f"{minutes:2}", fg="yellow") + " minutes " + + color(f"{seconds:2}", fg="yellow") + " seconds -----------") # fchk: 147 keys/s. Correct key found after 50% of candidates on average FCHK_KEYS_S = 147 @@ -272,7 +295,6 @@ minutes = int(foreseen_time // 60) seconds = int(foreseen_time % 60) print("Still about " + color(f"{minutes:2}", fg="yellow") + " minutes " + color(f"{seconds:2}", fg="yellow") + " seconds to run...") -start_time = time.time() abort = False print("Brute-forcing keys... Press any key to interrupt") @@ -437,11 +459,31 @@ else: if unknown: print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") + " ]-- has been inserted for unknown keys") + print(plus + "Generating final dump file") + dumpfile = f"hf-mf-{uid:08X}-dump.bin" + with (open(dumpfile, "wb")) as f: + for sec in range(NUM_SECTORS): + for b in range(4): + d = data[(sec * 4) + b] + if b == 3: + ka = found_keys[sec][0] + kb = found_keys[sec][1] + if ka == "": + ka = "FFFFFFFFFFFF" + if kb == "": + kb = "FFFFFFFFFFFF" + d = ka + d[12:20] + kb + f.write(bytes.fromhex(d)) + print(plus + "Data have been dumped to `" + color(dumpfile, fg="yellow")+"`") + +elapsed_time3 = time.time() - start_time - elapsed_time1 - elapsed_time2 +minutes = int(elapsed_time3 // 60) +seconds = int(elapsed_time3 % 60) +print("----Step 3: " + color(f"{minutes:2}", fg="yellow") + " minutes " + + color(f"{seconds:2}", fg="yellow") + " seconds -----------") elapsed_time = time.time() - start_time -minutes2 = int(elapsed_time // 60) -seconds2 = int(elapsed_time % 60) -print("----Step 2: " + color(f"{minutes2:2}", fg="yellow") + " minutes " + - color(f"{seconds2:2}", fg="yellow") + " seconds -----------") -print("---- TOTAL: " + color(f"{minutes1+minutes2:2}", fg="yellow") + " minutes " + - color(f"{seconds1+seconds2:2}", fg="yellow") + " seconds -----------") +minutes = int(elapsed_time // 60) +seconds = int(elapsed_time % 60) +print("---- TOTAL: " + color(f"{minutes:2}", fg="yellow") + " minutes " + + color(f"{seconds:2}", fg="yellow") + " seconds -----------") diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 44fd8f905..4ac174cf3 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -9727,7 +9727,10 @@ static int CmdHF14AMfISEN(const char *Cmd) { arg_lit0(NULL, "incblk2", "auth(blk)-auth(blk2)-auth(blk2+4)-..."), arg_lit0(NULL, "corruptnrar", "corrupt {nR}{aR}, but with correct parity"), arg_lit0(NULL, "corruptnrarparity", "correct {nR}{aR}, but with corrupted parity"), - arg_lit0(NULL, "collect_fm11rf08s", "correct all nT/{nT}/par_err of FM11RF08S in JSON. Option to be used only with -k."), + arg_rem("", ""), + arg_rem("FM11RF08S specific options:", "Incompatible with above options, except -k; output in JSON"), + arg_lit0(NULL, "collect_fm11rf08s", "collect all nT/{nT}/par_err."), + arg_lit0(NULL, "collect_fm11rf08s_with_data", "collect all nT/{nT}/par_err and data blocks."), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -9793,7 +9796,11 @@ static int CmdHF14AMfISEN(const char *Cmd) { bool incblk2 = arg_get_lit(ctx, 16); bool corruptnrar = arg_get_lit(ctx, 17); bool corruptnrarparity = arg_get_lit(ctx, 18); - bool collect_fm11rf08s = arg_get_lit(ctx, 19); + bool collect_fm11rf08s = arg_get_lit(ctx, 21); + bool collect_fm11rf08s_with_data = arg_get_lit(ctx, 22); + if (collect_fm11rf08s_with_data) { + collect_fm11rf08s = 1; + } CLIParserFree(ctx); uint8_t dbg_curr = DBG_NONE; @@ -9840,14 +9847,14 @@ static int CmdHF14AMfISEN(const char *Cmd) { } if (collect_fm11rf08s) { - SendCommandNG(CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES, key, sizeof(key)); + uint32_t flags = collect_fm11rf08s_with_data; + SendCommandMIX(CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES, flags, 0, 0, key, sizeof(key)); if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1000)) { if (resp.status == PM3_ESOFT) { return NONCE_FAIL; } } uint8_t num_sectors = 16; - // TODO: get nonces and display PrintAndLogEx(NORMAL, "[\n ["); for (uint8_t sec = 0; sec < num_sectors; sec++) { PrintAndLogEx(NORMAL, " [\"%08x\", \"%08x\"]%s", @@ -9871,12 +9878,30 @@ static int CmdHF14AMfISEN(const char *Cmd) { (p1 >> 3) & 1, (p1 >> 2) & 1, (p1 >> 1) & 1, p1 & 1, sec < num_sectors - 1 ? "," : ""); } - PrintAndLogEx(NORMAL, " ]\n]"); + if (collect_fm11rf08s_with_data) { + PrintAndLogEx(NORMAL, " ],\n ["); + int bytes = num_sectors * 4 * 16; - // PrintAndLogEx(SUCCESS, " nT: " _GREEN_("%s"), sprint_hex(resp.data.asBytes + (((sec * 2) + keyType) * 9), 4)); - // PrintAndLogEx(SUCCESS, " {nT}: " _GREEN_("%s"), sprint_hex(resp.data.asBytes + (((sec * 2) + keyType) * 9) + 4, 4)); - // // TODO: wrong par: - // PrintAndLogEx(SUCCESS, " par: " _GREEN_("%02x"), resp.data.asBytes[(((sec * 2) + keyType) * 9) + 8]); + uint8_t *dump = calloc(bytes, sizeof(uint8_t)); + if (dump == NULL) { + PrintAndLogEx(WARNING, "Fail, cannot allocate memory"); + return PM3_EFAILED; + } + if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) { + PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); + free(dump); + return PM3_ETIMEOUT; + } + for (uint8_t sec = 0; sec < num_sectors; sec++) { + for (uint8_t b = 0; b < 4; b++) { + PrintAndLogEx(NORMAL, " \"%s\"%s", + sprint_hex_inrow(dump + ((sec * 4) + b) * 16, 16), + (sec == num_sectors - 1) && (b == 3) ? "" : ","); + } + } + free(dump); + } + PrintAndLogEx(NORMAL, " ]\n]"); return PM3_SUCCESS; }