Merge pull request #3340 from team-orangeBlue/mfp_quickdump

Quick dump support in Mifare Plus
This commit is contained in:
Iceman
2026-05-29 19:40:12 +02:00
committed by GitHub
7 changed files with 332 additions and 119 deletions
+3
View File
@@ -3,6 +3,9 @@ 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]
- Fixed `hf mfp rdbl` when using "read multiple" blocks by decrypting the entire buffer instead of one block only (@team-orangeBlue)
- Improved `hf mfp dump` execution speed by removing crypto+card restarts, approx. 40% faster (@team-orangeBlue)
- Added support for non-first authentication in Mifare Plus (@team-orangeBlue)
- Added CUDA version of tools/mfulc_des_brute (@C2Pwn)
- Added `hf mfu desbrute` command: native client support for ULC key recovery (@C2Pwn)
- Added `hf mf sen` command: native client support for FM11RF08S SEN recovery (@C2Pwn)
+1 -1
View File
@@ -1163,7 +1163,7 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav
uint8_t *recv;
PacketResponseNG resp;
resend:
if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) {
if (WaitForResponseTimeout(CMD_ACK, &resp, 7000)) {
recv = resp.data.asBytes;
int iLen = resp.oldarg[0];
+1 -1
View File
@@ -6833,7 +6833,7 @@ static int CmdHF14AMfAuth4(const char *Cmd) {
return PM3_ESOFT;
}
return MifareAuth4(NULL, keyn, key, true, false, true, true, false);
return MifareAuth4(NULL, keyn, key, false, true, false, true, true, false);
}
// https://www.nxp.com/docs/en/application-note/AN10787.pdf
+235 -76
View File
@@ -779,7 +779,7 @@ static int CmdHFMFPAuth(const char *Cmd) {
CLIParserInit(&ctx, "hf mfp auth",
"Executes AES authentication command for MIFARE Plus card",
"hf mfp auth --ki 4000 --key 000102030405060708090a0b0c0d0e0f -> executes authentication\n"
"hf mfp auth --ki 9003 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v -> executes authentication and shows all the system data"
"hf mfp auth --ki 9003 --key FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v -> upgrades tag to SL3 and shows all the system data"
);
void *argtable[] = {
@@ -806,10 +806,13 @@ static int CmdHFMFPAuth(const char *Cmd) {
return PM3_EINVARG;
}
return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false);
int resBuffer = MifareAuth4(NULL, keyn, key, false, true, false, true, verbose, false);
if (resBuffer == PM3_SUCCESS && keyn[0] == 0x90 && (keyn[1] == 0x02 || keyn[1] == 0x03))
PrintAndLogEx(INFO, "Switched security level ( " _GREEN_("ok") " )");
return resBuffer;
}
int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) {
int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev, int bc) {
uint8_t kenc[MFBLOCK_SIZE];
memcpy(kenc, mf4session->Kenc, MFBLOCK_SIZE);
@@ -841,9 +844,9 @@ int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool
}
if (rev) {
aes_decode(IV, kenc, dati, dato, MFBLOCK_SIZE);
aes_decode(IV, kenc, dati, dato, MFBLOCK_SIZE*bc);
} else {
aes_encode(IV, kenc, dati, dato, MFBLOCK_SIZE);
aes_encode(IV, kenc, dati, dato, MFBLOCK_SIZE*bc);
}
return PM3_SUCCESS;
@@ -921,7 +924,7 @@ static int CmdHFMFPRdbl(const char *Cmd) {
}
mf4Session_t mf4session;
int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&mf4session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Authentication error: %d", res);
return res;
@@ -947,7 +950,7 @@ static int CmdHFMFPRdbl(const char *Cmd) {
}
if (plain == false) {
mfp_data_crypt(&mf4session, &data[1], &data[1], true);
mfp_data_crypt(&mf4session, &data[1], &data[1], true, blocksCount);
}
uint8_t sector = mfSectorNum(blockn);
@@ -1030,7 +1033,7 @@ static int CmdHFMFPRdsc(const char *Cmd) {
}
mf4Session_t mf4session;
int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&mf4session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Authentication error: %d", res);
return res;
@@ -1064,7 +1067,7 @@ static int CmdHFMFPRdsc(const char *Cmd) {
}
if (plain == false) {
mfp_data_crypt(&mf4session, &data[1], &data[1], true);
mfp_data_crypt(&mf4session, &data[1], &data[1], true, 1);
}
mf_print_block_one(blockno, data + 1, verbose);
@@ -1209,14 +1212,14 @@ static int CmdHFMFPWrbl(const char *Cmd) {
}
mf4Session_t mf4session;
int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&mf4session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Authentication error: %d", res);
return res;
}
if (plain == false) {
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false, 1);
}
uint8_t data[250] = {0};
@@ -1333,13 +1336,13 @@ static int CmdHFMFPChKey(const char *Cmd) {
PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
}
int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&mf4session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Authentication error: %d", res);
return res;
}
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false, 1);
uint8_t data[250] = {0};
int datalen = 0;
@@ -1451,13 +1454,13 @@ static int CmdHFMFPChConf(const char *Cmd) {
PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2));
}
int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&mf4session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Authentication error: %d", res);
return res;
}
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false);
mfp_data_crypt(&mf4session, &datain[0], &datain[0], false, 1);
uint8_t data[250] = {0};
int datalen = 0;
@@ -1548,7 +1551,7 @@ static int plus_key_check(uint8_t start_sector, uint8_t end_sector, uint8_t star
// authentication loop with retries
for (int retry = 0; retry < MFP_CHK_KEY_TRIES; retry++) {
res = MifareAuth4(NULL, keyn, currkey, selectCard, true, false, false, true);
res = MifareAuth4(NULL, keyn, currkey, false, selectCard, true, false, false, true);
if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER) {
break;
}
@@ -2023,6 +2026,7 @@ static int CmdHFMFPDump(const char *Cmd) {
arg_str0(NULL, "keys", "<fn>", "AES key file from `hf mfp chk --dump` (JSON)"),
arg_str0("k", "key", "<hex>", "AES key for all sectors (16 hex bytes)"),
arg_str0(NULL, "mfc-keys", "<fn>", "MFC key file for SL1 sectors (.bin from `hf mf chk`)"),
arg_lit0(NULL, "1k", "Chip is 1KB in size (Mifare Plus SE)"),
arg_lit0(NULL, "ns", "No save to file"),
arg_lit0("v", "verbose", "Verbose output"),
arg_param_end
@@ -2045,8 +2049,9 @@ static int CmdHFMFPDump(const char *Cmd) {
char mfc_key_fn[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)mfc_key_fn, FILE_PATH_SIZE, &mfckeyfnlen);
bool nosave = arg_get_lit(ctx, 5);
bool verbose = arg_get_lit(ctx, 6);
bool SE = arg_get_lit(ctx, 5);
bool nosave = arg_get_lit(ctx, 6);
bool verbose = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
@@ -2071,6 +2076,8 @@ static int CmdHFMFPDump(const char *Cmd) {
uint8_t numSectors;
if (ATQA & 0x0002) {
numSectors = MIFARE_4K_MAXSECTOR; // 40 sectors (4K)
} else if (SE) {
numSectors = MIFARE_1K_MAXSECTOR; // 16 sectors (1K)
} else {
numSectors = MIFARE_2K_MAXSECTOR; // 32 sectors (2K)
}
@@ -2079,7 +2086,7 @@ static int CmdHFMFPDump(const char *Cmd) {
PrintAndLogEx(INFO, "UID......... " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen));
PrintAndLogEx(INFO, "ATQA........ " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]);
PrintAndLogEx(INFO, "SAK......... " _GREEN_("%02X"), card.sak);
PrintAndLogEx(INFO, "Sectors..... " _GREEN_("%u") " (%s)", numSectors, (numSectors == MIFARE_4K_MAXSECTOR) ? "4K" : "2K");
PrintAndLogEx(INFO, "Sectors..... " _GREEN_("%u") " (%s)", numSectors, (numSectors == MIFARE_4K_MAXSECTOR) ? "4K" : (numSectors == MIFARE_1K_MAXSECTOR) ? "1K" : "2K");
PrintAndLogEx(NORMAL, "");
// ========================================
@@ -2190,6 +2197,7 @@ static int CmdHFMFPDump(const char *Cmd) {
// ========================================
// Determine SL for each sector based on which keys are available
bool quickread = true; // Reset if an SL1 key exists
uint8_t sectorSL[64];
memset(sectorSL, MFP_SL_UNKNOWN, sizeof(sectorSL));
for (uint8_t s = 0; s < numSectors; s++) {
@@ -2198,6 +2206,7 @@ static int CmdHFMFPDump(const char *Cmd) {
}
if (mfcFoundKeys[MF_KEY_A][s][0] || mfcFoundKeys[MF_KEY_B][s][0]) {
sectorSL[s] = MFP_SL_1;
quickread = false;
}
}
@@ -2221,65 +2230,211 @@ static int CmdHFMFPDump(const char *Cmd) {
int sectorsRead = 0;
int sl3Count = 0;
int sl1Count = 0;
for (uint8_t s = 0; s < numSectors; s++) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard");
break;
uint64_t t1 = msclock();
if (quickread) { // Auth to all sectors at once and read them out as quick as possible by analyzing ACLs + making the tag do as little crypto as possible
mf4Session_t _session;
// Cycle prep
uint8_t ki_pA[2] = {0x40, 0x00};
uint8_t ki_pB[2] = {0x40, 0x01};
uint8_t nullBlock[16] = {0};
uint8_t nullChunk[48] = {0};
// Partially unlock tag with all A keys for ACL reads
bool nonfirst = false;
uint8_t STBuffer[120];
int STRead;
uint8_t mac[8] = {0};
chunkCycle:
ki_pA[1] = sl3Count*2;
MifareAuth4(&_session, ki_pA, &aesFoundKeys[MF_KEY_A][sl3Count][1], nonfirst, !nonfirst, true, true, verbose, false);
nonfirst = true;
MFPReadBlock(&_session, false, false, true, 3+sl3Count*4, 1, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
PrintAndLogEx(ERR, "\nTrailer read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
goto chunkBlank;
}
bool readOK = false;
uint16_t blockOffset = mfFirstBlockOfSector(s);
uint8_t blocksInSector = mfNumBlocksPerSector(s);
if (sectorSL[s] == MFP_SL_3) {
// --- Try SL3 (AES) ---
for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) {
if (aesFoundKeys[kt][s][0] == 0) {
continue;
}
uint8_t sector_data[16 * 16] = {0};
res = mfpReadSector(s, kt, &aesFoundKeys[kt][s][1], sector_data, verbose);
if (res == PM3_SUCCESS) {
memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE);
sectorRead[s] = 1;
readOK = true;
sectorsRead++;
sl3Count++;
} else if (verbose) {
PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res);
}
}
} else if (sectorSL[s] == MFP_SL_1) {
// --- Try SL1 (CRYPTO1) ---
DropField();
for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) {
if (mfcFoundKeys[kt][s][0] == 0) {
continue;
}
uint8_t sector_data[16 * 16] = {0};
res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose);
if (res == PM3_SUCCESS) {
memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE);
sectorRead[s] = 1;
readOK = true;
sectorsRead++;
sl1Count++;
} else if (verbose) {
PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res);
}
}
if (STRead != 1 + 16 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
}
if (readOK) {
PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _GREEN_("ok, %s") " )",
s, numSectors - 1,
(sectorSL[s] == MFP_SL_3) ? "SL3" : "SL1");
mfp_data_crypt(&_session, &STBuffer[1], &STBuffer[1], true, 1);
// Multiblock reads do not allow reading out STs, as such this is the time to save them into the final dump
memcpy(carddata + ((3+4*sl3Count) * MFBLOCK_SIZE), &STBuffer[1], 1 * MFBLOCK_SIZE);
// Check if any block is encrypted only
ki_pB[1] = 0x01+sl3Count*2;
MifareAuth4(&_session, ki_pB, &aesFoundKeys[MF_KEY_B][sl3Count][1], true, false, true, true, verbose, false);
if (STBuffer[6] & 0xF0) { // At least one bit is set to force enc. only
MFPReadBlock(&_session, false, false, true, sl3Count*4, 3, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
chunkBlank:
PrintAndLogEx(ERR, "\nChunk read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
memcpy(carddata + (sl3Count * 4 * MFBLOCK_SIZE), nullChunk, 3 * MFBLOCK_SIZE);
memcpy(carddata + ((3+4*sl3Count) * MFBLOCK_SIZE), nullBlock, 1 * MFBLOCK_SIZE);
PrintAndLogEx(WARNING, "Quick-reading sector %3d / %3d ( " _RED_("fail") " )", sl3Count, numSectors - 1);
// Restart auth since a read failure resets it
nonfirst = false;
sl3Count++;
goto chunkCycleClean;
}
if (STRead != 1 + 48 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
DropField();
return PM3_ESOFT;
}
mfp_data_crypt(&_session, &STBuffer[1], &STBuffer[1], true, 3);
PrintAndLogEx(INPLACE, "Quick-reading sector %3d / %3d ( " _GREEN_("ok") " )", sl3Count, numSectors - 1);
memcpy(carddata + (sl3Count * 4 * MFBLOCK_SIZE), &STBuffer[1], 3 * MFBLOCK_SIZE);
sectorsRead++;
sl3Count++;
} else {
PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _RED_("fail") " )", s, numSectors - 1);
MFPReadBlock(&_session, true, false, true, sl3Count*4, 3, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
PrintAndLogEx(ERR, "Chunk read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
memcpy(carddata + (sl3Count * 4 * MFBLOCK_SIZE), nullChunk, 3 * MFBLOCK_SIZE);
PrintAndLogEx(WARNING, "Quick-reading sector %3d / %3d ( " _RED_("fail") " )", sl3Count, numSectors - 1);
goto chunkCycle;
}
if (STRead != 1 + 48 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
DropField();
return PM3_ESOFT;
}
PrintAndLogEx(INPLACE, "Quick-reading sector %3d / %3d ( " _GREEN_("ok") " )", sl3Count, numSectors - 1);
memcpy(carddata + (sl3Count * 4 * MFBLOCK_SIZE), &STBuffer[1], 3 * MFBLOCK_SIZE);
sectorsRead++;
sl3Count++;
}
chunkCycleClean:
if (sl3Count < MIN(32, numSectors)) goto chunkCycle; // Should stop at 16, or 32, or 32 then go to next cycle
// MifareAuth4(&_session, ki_pB, &aesFoundKeys[MF_KEY_B][0][1], true, false, true, true, verbose, false);
if (numSectors>32){
chunkCycle2:
ki_pA[1] = sl3Count*2;
MifareAuth4(&_session, ki_pA, &aesFoundKeys[MF_KEY_A][sl3Count][1], nonfirst, !nonfirst, true, true, verbose, false);
nonfirst = true;
MFPReadBlock(&_session, false, false, true, 128+(sl3Count-31)*16-1, 1, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
PrintAndLogEx(ERR, "Trailer read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
goto chunkBlank2;
return PM3_ESOFT;
}
if (STRead != 1 + 16 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
}
mfp_data_crypt(&_session, &STBuffer[1], &STBuffer[1], true, 1);
// Multiblock reads do not allow reading out STs, as such this is the time to save them into the final dump
memcpy(carddata + ((128+(sl3Count-31)*16-1) * MFBLOCK_SIZE), &STBuffer[1], 1 * MFBLOCK_SIZE);
int c = 0;
// Check if any block is encrypted only
ki_pB[1] = 0x01+sl3Count*2;
MifareAuth4(&_session, ki_pB, &aesFoundKeys[MF_KEY_B][sl3Count][1], true, false, true, true, verbose, false);
if (STBuffer[6] & 0xF0) { // At least one bit is set to force enc. only
chunkBlank2: // Jumping here will start the cycle which will blank out the remaining 5 chunks anyway
for (c=0; c<5; ++c) {
MFPReadBlock(&_session, false, false, true, 128+(sl3Count-32)*16+c*3, 3, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
PrintAndLogEx(ERR, "Chunk read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
PrintAndLogEx(WARNING, "Quick-reading sector %3d / %3d chunk %d ( " _RED_("fail") " )", sl3Count, numSectors - 1, c);
memcpy(carddata + ((128+(sl3Count-31)*16-1) * MFBLOCK_SIZE), nullBlock, 1 * MFBLOCK_SIZE);
memcpy(carddata + ((128 + 16*(sl3Count-32)+c*3) * MFBLOCK_SIZE), nullChunk, 3 * MFBLOCK_SIZE);
nonfirst = false;
if (c<5) {continue;} else {sl3Count++; goto chunkCycleClean2;};
}
if (STRead != 1 + 48 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
DropField();
return PM3_ESOFT;
}
mfp_data_crypt(&_session, &STBuffer[1], &STBuffer[1], true, 3);
PrintAndLogEx(INPLACE, "Quick-reading sector %3d / %3d ( " _GREEN_("ok") " )", sl3Count, numSectors - 1);
memcpy(carddata + ((128 + 16*(sl3Count-32)+c*3) * MFBLOCK_SIZE), &STBuffer[1], 3 * MFBLOCK_SIZE);
}
sectorsRead++;
sl3Count++;
} else {
for (c=0; c<5; ++c) {
MFPReadBlock(&_session, true, false, true, 128+(sl3Count-32)*16+c*3, 3, false, true, STBuffer, sizeof(STBuffer), &STRead, mac);
if (STRead && STBuffer[0] != 0x90) {
PrintAndLogEx(ERR, "Chunk read error: %02x %s", STBuffer[0], mfpGetErrorDescription(STBuffer[0]));
memcpy(carddata + ((128 + 16*(sl3Count-32)+c*3) * MFBLOCK_SIZE), nullChunk, 3 * MFBLOCK_SIZE);
PrintAndLogEx(WARNING, "Quick-reading sector %3d / %3d ( " _RED_("fail") " )", sl3Count, numSectors - 1);
continue;
}
if (STRead != 1 + 48 + 2) {
PrintAndLogEx(ERR, "Error return length: %d", STRead);
DropField();
return PM3_ESOFT;
}
PrintAndLogEx(INPLACE, "Quick-reading sector %3d / %3d ( " _GREEN_("ok") " )", sl3Count, numSectors - 1);
memcpy(carddata + ((128 + 16*(sl3Count-32)+c*3) * MFBLOCK_SIZE), &STBuffer[1], 3 * MFBLOCK_SIZE);
}
sectorsRead++;
sl3Count++;
}
chunkCycleClean2:
if (sl3Count < numSectors) goto chunkCycle2;
}
} else {
for (uint8_t s = 0; s < numSectors; s++) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard");
break;
}
bool readOK = false;
uint16_t blockOffset = mfFirstBlockOfSector(s);
uint8_t blocksInSector = mfNumBlocksPerSector(s);
if (sectorSL[s] == MFP_SL_3) {
// --- Try SL3 (AES) ---
for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) {
if (aesFoundKeys[kt][s][0] == 0) {
continue;
}
uint8_t sector_data[16 * 16] = {0};
res = mfpReadSector(s, kt, &aesFoundKeys[kt][s][1], sector_data, verbose);
if (res == PM3_SUCCESS) {
memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE);
sectorRead[s] = 1;
readOK = true;
sectorsRead++;
sl3Count++;
} else if (verbose) {
PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res);
}
}
} else if (sectorSL[s] == MFP_SL_1) {
// --- Try SL1 (CRYPTO1) ---
DropField();
for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) {
if (mfcFoundKeys[kt][s][0] == 0) {
continue;
}
uint8_t sector_data[16 * 16] = {0};
res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose);
if (res == PM3_SUCCESS) {
memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE);
sectorRead[s] = 1;
readOK = true;
sectorsRead++;
sl1Count++;
} else if (verbose) {
PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res);
}
}
}
if (readOK) {
PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _GREEN_("ok, %s") " )",
s, numSectors - 1,
(sectorSL[s] == MFP_SL_3) ? "SL3" : "SL1");
} else {
PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _RED_("fail") " )", s, numSectors - 1);
}
}
}
@@ -2288,6 +2443,9 @@ static int CmdHFMFPDump(const char *Cmd) {
PrintAndLogEx(INFO, "Successfully read " _GREEN_("%d") " / %d sectors (SL3: %d, SL1: %d)", sectorsRead, numSectors, sl3Count, sl1Count);
PrintAndLogEx(NORMAL, "");
DropField();
t1 = msclock() - t1;
// ========================================
// Print sector summary
// ========================================
@@ -2388,7 +2546,8 @@ static int CmdHFMFPDump(const char *Cmd) {
PrintAndLogEx(HINT, "Partial dump: %d of %d sectors read", sectorsRead, numSectors);
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`hf mfp chk --dump`") " and/or " _YELLOW_("`hf mf chk`") " to find more keys");
}
PrintAndLogEx(INFO, "\ntime in dump " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0);
free(carddata);
return PM3_SUCCESS;
}
+1 -1
View File
@@ -33,5 +33,5 @@ typedef struct mfp_keys {
int CmdHFMFP(const char *Cmd);
int CmdHFMFPNDEFRead(const char *Cmd);
int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev);
int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev, int bc);
#endif
+90 -39
View File
@@ -204,23 +204,28 @@ int CalculateMAC(mf4Session_t *mf4session, MACType_t mtype, uint8_t blockNum, ui
return aes_cmac8(NULL, mf4session->Kmac, macdata, mac, macdatalen);
}
int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode) {
int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, bool nonfirst, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode) {
uint8_t data[257] = {0};
int datalen = 0;
uint8_t RndA[17] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00};
if (nonfirst && memcmp(mf4session->Kmac, data, 16) == 0) { // compiler abuse
PrintAndLogEx(WARNING, "Function invocation error: cannot do non-first authentication yet");
PrintAndLogEx(HINT, "Try to do first authentication");
return PM3_EINVARG;
} // While TI maybe could be rolled as a zero, MACing key absolutely won't be. Don't tell me it'll happen, it won't, the chances are effectively zero.
uint8_t RndA[17] = {0x50, 0x4D, 0x33, 0x20, 0x52, 0x52, 0x47, 0x20, 0x32, 0x30, 0x32, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t RndB[17] = {0};
if (silentMode) {
verbose = false;
}
if (mf4session) {
if (mf4session && !nonfirst) {
mf4session->Authenticated = false;
}
uint8_t cmd1[] = {0x70, keyn[1], keyn[0], 0x00};
int res = ExchangeRAW14a(cmd1, sizeof(cmd1), activateField, true, data, sizeof(data), &datalen, silentMode);
uint8_t cmd1[4];
if (nonfirst) { cmd1[0] = 0x76; cmd1[1] = keyn[1]; cmd1[2] = keyn[0]; } else { cmd1[0] = 0x70; cmd1[1] = keyn[1]; cmd1[2] = keyn[0]; cmd1[3] = 0x00; }
int res = ExchangeRAW14a(cmd1, nonfirst ? 3 : 4, activateField, true, data, sizeof(data), &datalen, silentMode);
if (res != PM3_SUCCESS) {
if (silentMode == false) {
@@ -234,6 +239,7 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
}
if (verbose) {
PrintAndLogEx(INFO, ">phase1: %s", sprint_hex(cmd1, nonfirst ? 3 : 4));
PrintAndLogEx(INFO, "< phase1: %s", sprint_hex(data, datalen));
}
@@ -259,7 +265,7 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
return PM3_EWRONGANSWER;
}
if (datalen != 19) { // code 1b + 16b + crc 2b
if (datalen != 19) { // code 1b + Rnd 16b + crc 2b
if (silentMode == false) {
PrintAndLogEx(ERR, "Card response must be 19 bytes long instead of: %d", datalen);
}
@@ -270,22 +276,65 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
return PM3_EWRONGANSWER;
}
aes_decode(NULL, key, &data[1], RndB, 16);
RndB[16] = RndB[0];
if (verbose) {
PrintAndLogEx(INFO, "RndB: %s", sprint_hex(RndB, 16));
}
uint8_t cmd2[33] = {0};
cmd2[0] = 0x72;
uint8_t raw[32] = {0};
memmove(raw, RndA, 16);
memmove(&raw[16], &RndB[1], 16);
uint8_t IVR[16];
uint8_t IVW[16];
// Non-first auth applies all the wild sorcery from the encryption magic (replies have "read IVs", commands must have "write IVs").
// To save instructions I'm going to just do one big if check
if (nonfirst) {
// WARNING TO IMPLEMENTERS
// This code is in theory NOT accurate to the confidential datasheet for Mifare Plus.
// Refer to proper IV generation in commit f29c94954f0d4958ba7947d11a44f61c900d4168.
memcpy(&IVR[0], &mf4session->R_Ctr, 2);
memcpy(&IVR[2], &mf4session->W_Ctr, 2);
memcpy(&IVR[4], &mf4session->R_Ctr, 2);
memcpy(&IVR[6], &mf4session->W_Ctr, 2);
memcpy(&IVR[8], &mf4session->R_Ctr, 2);
memcpy(&IVR[10], &mf4session->W_Ctr, 2);
memcpy(&IVR[12], &mf4session->R_Ctr, 2);
memcpy(&IVR[14], &mf4session->W_Ctr, 2);
memcpy(&IVR[12], mf4session->TI, 4);
aes_encode(NULL, key, raw, &cmd2[1], 32);
if (verbose) {
PrintAndLogEx(INFO, ">phase2: %s", sprint_hex(cmd2, 33));
memcpy(&IVW[0], &mf4session->R_Ctr, 2);
memcpy(&IVW[2], &mf4session->W_Ctr, 2);
memcpy(&IVW[4], &mf4session->R_Ctr, 2);
memcpy(&IVW[6], &mf4session->W_Ctr, 2);
memcpy(&IVW[8], &mf4session->R_Ctr, 2);
memcpy(&IVW[10], &mf4session->W_Ctr, 2);
memcpy(&IVW[12], &mf4session->R_Ctr, 2);
memcpy(&IVW[14], &mf4session->W_Ctr, 2);
memcpy(IVW, mf4session->TI, 4);
aes_decode(IVR, key, &data[1], RndB, 16);
RndB[16] = RndB[0];
if (verbose) {
PrintAndLogEx(INFO, "RndB: %s", sprint_hex(RndB, 16));
}
memmove(raw, RndA, 16);
memmove(&raw[16], &RndB[1], 16);
aes_encode(IVW, key, raw, &cmd2[1], 32);
if (verbose) {
PrintAndLogEx(INFO, ">phase2: %s", sprint_hex(cmd2, 33));
}
} else {
aes_decode(NULL, key, &data[1], RndB, 16);
RndB[16] = RndB[0];
if (verbose) {
PrintAndLogEx(INFO, "RndB: %s", sprint_hex(RndB, 16));
}
memmove(raw, RndA, 16);
memmove(&raw[16], &RndB[1], 16);
aes_encode(NULL, key, raw, &cmd2[1], 32);
if (verbose) {
PrintAndLogEx(INFO, ">phase2: %s", sprint_hex(cmd2, 33));
}
}
res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen, silentMode);
@@ -304,21 +353,9 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
PrintAndLogEx(INFO, "< phase2: %s", sprint_hex(data, datalen));
}
aes_decode(NULL, key, &data[1], raw, 32);
if (verbose) {
PrintAndLogEx(INFO, "res: %s", sprint_hex(raw, 32));
PrintAndLogEx(INFO, "RndA`: %s", sprint_hex(&raw[4], 16));
}
if (memcmp(&raw[4], &RndA[1], 16)) {
if (data[0]!=0x90) {
if (silentMode == false) {
PrintAndLogEx(ERR, "\nAuthentication FAILED. rnd is not equal");
}
if (verbose) {
PrintAndLogEx(ERR, "RndA reader: %s", sprint_hex(&RndA[1], 16));
PrintAndLogEx(ERR, "RndA card: %s", sprint_hex(&raw[4], 16));
PrintAndLogEx(ERR, "\nAuthentication FAILED. Card did not ACK response");
}
if (dropFieldIfError) {
@@ -327,7 +364,19 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
return PM3_EWRONGANSWER;
}
if (nonfirst) { aes_decode(IVR, key, &data[1], raw, 16); } else { aes_decode(NULL, key, &data[1], raw, 32); }
if (verbose) {
if (nonfirst) {
PrintAndLogEx(INFO, "res: %s", sprint_hex(raw, 16));
PrintAndLogEx(INFO, "RndA`: %s", sprint_hex(&raw[0], 16));
} else {
PrintAndLogEx(INFO, "res: %s", sprint_hex(raw, 32));
PrintAndLogEx(INFO, "RndA`: %s", sprint_hex(&raw[4], 16));
}
}
if (verbose && !nonfirst) {
PrintAndLogEx(INFO, " TI: %s", sprint_hex(raw, 4));
PrintAndLogEx(INFO, "pic: %s", sprint_hex(&raw[20], 6));
PrintAndLogEx(INFO, "pcd: %s", sprint_hex(&raw[26], 6));
@@ -371,17 +420,19 @@ int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, boo
if (mf4session) {
mf4session->Authenticated = true;
mf4session->R_Ctr = 0;
mf4session->W_Ctr = 0;
mf4session->KeyNum = keyn[1] + (keyn[0] << 8);
memmove(mf4session->RndA, RndA, 16);
memmove(mf4session->RndB, RndB, 16);
memmove(mf4session->Key, key, 16);
memmove(mf4session->TI, raw, 4);
memmove(mf4session->PICCap2, &raw[20], 6);
memmove(mf4session->PCDCap2, &raw[26], 6);
memmove(mf4session->Kenc, kenc, 16);
memmove(mf4session->Kmac, kmac, 16);
if (!nonfirst) {
mf4session->R_Ctr = 0;
mf4session->W_Ctr = 0;
memmove(mf4session->TI, raw, 4);
memmove(mf4session->PICCap2, &raw[20], 6);
memmove(mf4session->PCDCap2, &raw[26], 6);
}
}
if (verbose) {
@@ -505,7 +556,7 @@ int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *data
}
mf4Session_t _session;
int res = MifareAuth4(&_session, keyn, key, true, true, true, verbose, false);
int res = MifareAuth4(&_session, keyn, key, false, true, true, true, verbose, false);
if (res) {
PrintAndLogEx(ERR, "Sector %u authentication error: %d", sectorNo, res);
return res;
@@ -535,7 +586,7 @@ int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *data
}
// Encrypted mode is always used. Doing an if to check will waste instructions
mfp_data_crypt(&_session, &data[1], &data[1], true);
mfp_data_crypt(&_session, &data[1], &data[1], true, 1);
memcpy(&dataout[(n - firstBlockNo) * 16], &data[1], 16);
+1 -1
View File
@@ -60,7 +60,7 @@ void mfpSetVerboseMode(bool verbose);
const char *mfpGetErrorDescription(uint8_t errorCode);
int CalculateMAC(mf4Session_t *mf4session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose);
int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode);
int MifareAuth4(mf4Session_t *mf4session, const uint8_t *keyn, uint8_t *key, bool nonfirst, bool activateField, bool leaveSignalON, bool dropFieldIfError, bool verbose, bool silentMode);
int MFPWritePerso(const uint8_t *keyNum, const uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen);
int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen);