Added support for collecting all fm11rf08s nT/{nT}/par_err at once

This commit is contained in:
Philippe Teuwen 2024-09-02 23:11:36 +02:00
commit c73e2ea623
7 changed files with 192 additions and 37 deletions

View file

@ -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 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)

View file

@ -1750,6 +1750,10 @@ static void PacketReceived(PacketCommandNG *packet) {
MifareAcquireEncryptedNonces(packet->oldarg[0], packet->oldarg[1], packet->oldarg[2], packet->data.asBytes);
break;
}
case CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES: {
MifareAcquireStaticEncryptedNonces(packet->data.asBytes);
break;
}
case CMD_HF_MIFARE_ACQ_NONCES: {
MifareAcquireNonces(packet->oldarg[0], packet->oldarg[2]);
break;

View file

@ -1032,6 +1032,140 @@ 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) {
struct Crypto1State mpcs = {0, 0};
struct Crypto1State *pcs;
pcs = &mpcs;
uint8_t uid[10] = {0x00};
uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE] = {0x00};
uint8_t par_enc[1] = {0x00};
uint8_t buf[PM3_CMD_DATA_SIZE] = {0x00};
uint64_t ui64Key = bytes_to_num(key, 6);
uint32_t cuid = 0;
int16_t isOK = PM3_SUCCESS;
uint16_t num_nonces = 0;
uint8_t cascade_levels = 0;
bool have_uid = false;
uint8_t num_sectors = 16;
LED_A_ON();
LED_C_OFF();
BigBuf_free();
BigBuf_Clear_ext(false);
clear_trace();
set_tracing(false);
iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
LED_C_ON();
for (uint16_t sec = 0; sec < num_sectors; sec++) {
uint8_t blockNo = sec * 4;
for (uint8_t keyType = 0; keyType < 2; keyType++) {
// Test if the action was cancelled
if (BUTTON_PRESS()) {
isOK = PM3_EOPABORTED;
break;
}
if (have_uid == false) { // need a full select cycle to get the uid first
iso14a_card_select_t card_info;
if (iso14443a_select_card(uid, &card_info, &cuid, true, 0, true) == 0) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (ALL)");
continue;
}
switch (card_info.uidlen) {
case 4 :
cascade_levels = 1;
break;
case 7 :
cascade_levels = 2;
break;
case 10:
cascade_levels = 3;
break;
default:
break;
}
have_uid = true;
} else { // no need for anticollision. We can directly select the card
if (iso14443a_fast_select_card(uid, cascade_levels) == 0) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (UID)");
continue;
}
}
uint32_t nt1 = 0;
if (mifare_classic_authex_cmd(pcs, cuid, blockNo, MIFARE_AUTH_KEYA + keyType + 4, ui64Key, AUTH_FIRST, &nt1, NULL, NULL, NULL, false, false)) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth1 error");
isOK = PM3_ESOFT;
goto out;
};
// nested authentication
uint16_t len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType + 4, blockNo, receivedAnswer, par_enc, NULL);
if (len != 4) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth2 error len=%d", len);
isOK = PM3_ESOFT;
goto out;
}
uint32_t nt_enc = bytes_to_num(receivedAnswer, 4);
crypto1_init(pcs, ui64Key);
uint32_t nt = crypto1_word(pcs, nt_enc ^ cuid, 1) ^ nt_enc;
// Dbprintf("Sec %2i key %i nT=%08x", sec, keyType + 4, nt);
num_to_bytes(nt, 4, buf + (((sec * 2) + keyType) * 9));
// send some crap to fail auth
uint8_t nack[] = {0x04};
ReaderTransmit(nack, sizeof(nack), NULL);
if (iso14443a_fast_select_card(uid, cascade_levels) == 0) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (UID)");
continue;
}
if (mifare_classic_authex_cmd(pcs, cuid, blockNo, MIFARE_AUTH_KEYA + keyType + 4, ui64Key, AUTH_FIRST, &nt1, NULL, NULL, NULL, false, false)) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth1 error");
isOK = PM3_ESOFT;
goto out;
};
// nested authentication on regular keytype
len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType, blockNo, receivedAnswer, par_enc, NULL);
if (len != 4) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth2 error len=%d", len);
isOK = PM3_ESOFT;
goto out;
}
memcpy(buf + (((sec * 2) + keyType) * 9) + 4, receivedAnswer, 4);
nt_enc = bytes_to_num(receivedAnswer, 4);
uint8_t nt_par_err = ((((par_enc[0] >> 7) & 1) ^ oddparity8((nt_enc >> 24) & 0xFF)) << 3 |
(((par_enc[0] >> 6) & 1) ^ oddparity8((nt_enc >> 16) & 0xFF)) << 2 |
(((par_enc[0] >> 5) & 1) ^ oddparity8((nt_enc >> 8) & 0xFF)) << 1 |
(((par_enc[0] >> 4) & 1) ^ oddparity8((nt_enc >> 0) & 0xFF)));
// Dbprintf("Sec %2i key %i {nT}=%02x%02x%02x%02x perr=%x", sec, keyType, receivedAnswer[0], receivedAnswer[1], receivedAnswer[2], receivedAnswer[3], nt_par_err);
buf[(((sec * 2) + keyType) * 9) + 8] = nt_par_err;
// send some crap to fail auth
ReaderTransmit(nack, sizeof(nack), NULL);
}
}
out:
LED_C_OFF();
crypto1_deinit(pcs);
LED_B_ON();
reply_old(CMD_ACK, isOK, cuid, num_nonces, buf, sizeof(buf));
LED_B_OFF();
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
LEDsoff();
set_tracing(false);
}
//-----------------------------------------------------------------------------
// MIFARE nested authentication.
//

View file

@ -37,6 +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 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);

View file

@ -18,6 +18,7 @@ import sys
import time
import subprocess
import argparse
import json
import pm3
# optional color support
try:
@ -101,44 +102,14 @@ if args.init_check:
found_keys[sec][1] = res[4]
print_key(sec, 1, found_keys[sec][1])
nt = [["", ""] for _ in range(NUM_SECTORS)]
nt_enc = [["", ""] for _ in range(NUM_SECTORS)]
par_err = [["", ""] for _ in range(NUM_SECTORS)]
print("Getting nonces...")
for sec in range(NUM_SECTORS):
blk = sec * 4
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
# Even if one key already found, we'll need both nt
for key_type in [0, 1]:
cmd = f"hf mf isen -n1 --blk {blk} -c {key_type+4} --key {BACKDOOR_RF08S}"
p.console(cmd)
cmd += f" --c2 {key_type}"
p.console(cmd)
print("Processing traces...")
for line in p.grabbed_output.split('\n'):
if "nested cmd: 64" in line or "nested cmd: 65" in line:
sec = int(line[24:26], 16)//4
key_type = int(line[21:23], 16) - 0x64
data = line[65:73]
nt[sec][key_type] = data
if "nested cmd: 60" in line or "nested cmd: 61" in line:
sec = int(line[24:26], 16)//4
key_type = int(line[21:23], 16) - 0x60
data = line[108:116]
nt_enc[sec][key_type] = data
data = line[128:136]
par_err[sec][key_type] = data
# Check if we got all nonces, else abort.
# TODO: retry instead...
for sec in range(NUM_SECTORS):
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
for key_type in [0, 1]:
if (nt[sec][key_type] == "" or
nt_enc[sec][key_type] == "" or
par_err[sec][key_type] == ""):
print("Error, could not collect all nonces, try again.")
exit()
cmd = f"hf mf isen --collect_fm11rf08s --key {BACKDOOR_RF08S}"
p.console(cmd)
try:
nt, nt_enc, par_err = json.loads(p.grabbed_output)
except json.decoder.JSONDecodeError:
print("Error getting nonces, abort.")
exit()
if os.path.isfile(DICT_DEF_PATH):
print(f"Loading {DICT_DEF}")

View file

@ -9727,6 +9727,7 @@ 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_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@ -9792,6 +9793,7 @@ 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);
CLIParserFree(ctx);
uint8_t dbg_curr = DBG_NONE;
@ -9837,6 +9839,47 @@ static int CmdHF14AMfISEN(const char *Cmd) {
PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision");
}
if (collect_fm11rf08s) {
SendCommandNG(CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES, 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",
bytes_to_num(resp.data.asBytes + ((sec * 2) * 9), 4),
bytes_to_num(resp.data.asBytes + (((sec * 2) + 1) * 9), 4),
sec < num_sectors - 1 ? "," : "");
}
PrintAndLogEx(NORMAL, " ],\n [");
for (uint8_t sec = 0; sec < num_sectors; sec++) {
PrintAndLogEx(NORMAL, " [\"%08x\", \"%08x\"]%s",
bytes_to_num(resp.data.asBytes + ((sec * 2) * 9) + 4, 4),
bytes_to_num(resp.data.asBytes + (((sec * 2) + 1) * 9) + 4, 4),
sec < num_sectors - 1 ? "," : "");
}
PrintAndLogEx(NORMAL, " ],\n [");
for (uint8_t sec = 0; sec < num_sectors; sec++) {
uint8_t p0 = resp.data.asBytes[((sec * 2) * 9) + 8];
uint8_t p1 = resp.data.asBytes[(((sec * 2) + 1) * 9) + 8];
PrintAndLogEx(NORMAL, " [\"%i%i%i%i\", \"%i%i%i%i\"]%s",
(p0 >> 3) & 1, (p0 >> 2) & 1, (p0 >> 1) & 1, p0 & 1,
(p1 >> 3) & 1, (p1 >> 2) & 1, (p1 >> 1) & 1, p1 & 1,
sec < num_sectors - 1 ? "," : "");
}
PrintAndLogEx(NORMAL, " ]\n]");
// 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]);
return PM3_SUCCESS;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("ISO14443-a Information") " ---------------------");
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen));

View file

@ -677,6 +677,7 @@ typedef struct {
#define CMD_HF_MIFARE_ACQ_NONCES 0x0614
#define CMD_HF_MIFARE_STATIC_NESTED 0x0615
#define CMD_HF_MIFARE_STATIC_ENC 0x0616
#define CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES 0x0617
#define CMD_HF_MIFARE_READBL 0x0620
#define CMD_HF_MIFARE_READBL_EX 0x0628