Added support for dumping FM11RF08S data at once

This commit is contained in:
Philippe Teuwen 2024-09-03 11:43:22 +02:00
commit de86cd85d1
6 changed files with 110 additions and 27 deletions

View file

@ -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... 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] ## [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) - Fixed `hf mfu wrbl` - compatibility write only writes 4 bytes. Now handled correctly (@iceman1001)
- Changed `hf mfu info` - better magic tag detection (@iceman1001) - Changed `hf mfu info` - better magic tag detection (@iceman1001)
- Added ELECTRA pattern decoding in `lf search` (@CiRIP) - Added ELECTRA pattern decoding in `lf search` (@CiRIP)

View file

@ -1751,7 +1751,7 @@ static void PacketReceived(PacketCommandNG *packet) {
break; break;
} }
case CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES: { case CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES: {
MifareAcquireStaticEncryptedNonces(packet->data.asBytes); MifareAcquireStaticEncryptedNonces(packet->oldarg[0], packet->data.asBytes);
break; break;
} }
case CMD_HF_MIFARE_ACQ_NONCES: { case CMD_HF_MIFARE_ACQ_NONCES: {

View file

@ -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 // acquire static encrypted nonces in order to perform the attack described in
// Philippe Teuwen, "MIFARE Classic: exposing the static encrypted nonce variant" // 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 mpcs = {0, 0};
struct Crypto1State *pcs; struct Crypto1State *pcs;
@ -1048,6 +1048,7 @@ void MifareAcquireStaticEncryptedNonces(uint8_t *key) {
uint8_t buf[PM3_CMD_DATA_SIZE] = {0x00}; uint8_t buf[PM3_CMD_DATA_SIZE] = {0x00};
uint64_t ui64Key = bytes_to_num(key, 6); uint64_t ui64Key = bytes_to_num(key, 6);
bool with_data = flags & 1;
uint32_t cuid = 0; uint32_t cuid = 0;
int16_t isOK = PM3_SUCCESS; int16_t isOK = PM3_SUCCESS;
uint16_t num_nonces = 0; uint16_t num_nonces = 0;
@ -1108,6 +1109,20 @@ void MifareAcquireStaticEncryptedNonces(uint8_t *key) {
isOK = PM3_ESOFT; isOK = PM3_ESOFT;
goto out; 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 // nested authentication
uint16_t len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType + 4, blockNo, receivedAnswer, par_enc, NULL); uint16_t len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType + 4, blockNo, receivedAnswer, par_enc, NULL);
if (len != 4) { if (len != 4) {

View file

@ -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 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 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 MifareAcquireNonces(uint32_t arg0, uint32_t flags);
void MifareChkKeys(uint8_t *datain, uint8_t reserved_mem); void MifareChkKeys(uint8_t *datain, uint8_t reserved_mem);
void MifareChkKeys_fast(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); void MifareChkKeys_fast(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain);

View file

@ -103,14 +103,37 @@ if args.init_check:
print_key(sec, 1, found_keys[sec][1]) print_key(sec, 1, found_keys[sec][1])
print("Getting nonces...") 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) p.console(cmd)
try: 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: except json.decoder.JSONDecodeError:
print("Error getting nonces, abort.") print("Error getting nonces, abort.")
exit() 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): if os.path.isfile(DICT_DEF_PATH):
print(f"Loading {DICT_DEF}") print(f"Loading {DICT_DEF}")
with open(DICT_DEF_PATH, 'r', encoding='utf-8') as file: 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} ") 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)) total_candidates = sum(candidates[sec][0] + candidates[sec][1] for sec in range(NUM_SECTORS))
elapsed_time = time.time() - start_time elapsed_time2 = time.time() - start_time - elapsed_time1
minutes1 = int(elapsed_time // 60) minutes = int(elapsed_time2 // 60)
seconds1 = int(elapsed_time % 60) seconds = int(elapsed_time2 % 60)
print("----Step 1: " + color(f"{minutes1:2}", fg="yellow") + " minutes " + print("----Step 2: " + color(f"{minutes:2}", fg="yellow") + " minutes " +
color(f"{seconds1:2}", fg="yellow") + " seconds -----------") color(f"{seconds:2}", fg="yellow") + " seconds -----------")
# fchk: 147 keys/s. Correct key found after 50% of candidates on average # fchk: 147 keys/s. Correct key found after 50% of candidates on average
FCHK_KEYS_S = 147 FCHK_KEYS_S = 147
@ -272,7 +295,6 @@ minutes = int(foreseen_time // 60)
seconds = int(foreseen_time % 60) seconds = int(foreseen_time % 60)
print("Still about " + color(f"{minutes:2}", fg="yellow") + " minutes " + print("Still about " + color(f"{minutes:2}", fg="yellow") + " minutes " +
color(f"{seconds:2}", fg="yellow") + " seconds to run...") color(f"{seconds:2}", fg="yellow") + " seconds to run...")
start_time = time.time()
abort = False abort = False
print("Brute-forcing keys... Press any key to interrupt") print("Brute-forcing keys... Press any key to interrupt")
@ -437,11 +459,31 @@ else:
if unknown: if unknown:
print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") + print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") +
" ]-- has been inserted for unknown keys") " ]-- 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 elapsed_time = time.time() - start_time
minutes2 = int(elapsed_time // 60) minutes = int(elapsed_time // 60)
seconds2 = int(elapsed_time % 60) seconds = int(elapsed_time % 60)
print("----Step 2: " + color(f"{minutes2:2}", fg="yellow") + " minutes " + print("---- TOTAL: " + color(f"{minutes:2}", fg="yellow") + " minutes " +
color(f"{seconds2:2}", fg="yellow") + " seconds -----------") color(f"{seconds:2}", fg="yellow") + " seconds -----------")
print("---- TOTAL: " + color(f"{minutes1+minutes2:2}", fg="yellow") + " minutes " +
color(f"{seconds1+seconds2:2}", fg="yellow") + " seconds -----------")

View file

@ -9727,7 +9727,10 @@ static int CmdHF14AMfISEN(const char *Cmd) {
arg_lit0(NULL, "incblk2", "auth(blk)-auth(blk2)-auth(blk2+4)-..."), 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, "corruptnrar", "corrupt {nR}{aR}, but with correct parity"),
arg_lit0(NULL, "corruptnrarparity", "correct {nR}{aR}, but with corrupted 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 arg_param_end
}; };
CLIExecWithReturn(ctx, Cmd, argtable, true); CLIExecWithReturn(ctx, Cmd, argtable, true);
@ -9793,7 +9796,11 @@ static int CmdHF14AMfISEN(const char *Cmd) {
bool incblk2 = arg_get_lit(ctx, 16); bool incblk2 = arg_get_lit(ctx, 16);
bool corruptnrar = arg_get_lit(ctx, 17); bool corruptnrar = arg_get_lit(ctx, 17);
bool corruptnrarparity = arg_get_lit(ctx, 18); 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); CLIParserFree(ctx);
uint8_t dbg_curr = DBG_NONE; uint8_t dbg_curr = DBG_NONE;
@ -9840,14 +9847,14 @@ static int CmdHF14AMfISEN(const char *Cmd) {
} }
if (collect_fm11rf08s) { 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 (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1000)) {
if (resp.status == PM3_ESOFT) { if (resp.status == PM3_ESOFT) {
return NONCE_FAIL; return NONCE_FAIL;
} }
} }
uint8_t num_sectors = 16; uint8_t num_sectors = 16;
// TODO: get nonces and display
PrintAndLogEx(NORMAL, "[\n ["); PrintAndLogEx(NORMAL, "[\n [");
for (uint8_t sec = 0; sec < num_sectors; sec++) { for (uint8_t sec = 0; sec < num_sectors; sec++) {
PrintAndLogEx(NORMAL, " [\"%08x\", \"%08x\"]%s", 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, (p1 >> 3) & 1, (p1 >> 2) & 1, (p1 >> 1) & 1, p1 & 1,
sec < num_sectors - 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)); uint8_t *dump = calloc(bytes, sizeof(uint8_t));
// PrintAndLogEx(SUCCESS, " {nT}: " _GREEN_("%s"), sprint_hex(resp.data.asBytes + (((sec * 2) + keyType) * 9) + 4, 4)); if (dump == NULL) {
// // TODO: wrong par: PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
// PrintAndLogEx(SUCCESS, " par: " _GREEN_("%02x"), resp.data.asBytes[(((sec * 2) + keyType) * 9) + 8]); 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; return PM3_SUCCESS;
} }