Merge branch 'RfidResearchGroup:master' into master

This commit is contained in:
ry4000 2025-06-02 17:50:07 +10:00 committed by GitHub
commit 34ddd4a75c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1425 additions and 275 deletions

View file

@ -3,17 +3,22 @@ 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]
- Changed `hf iclass tear` - readability improvements for erase phase (@antiklesys)
- Changed `hf iclass legrec` - code optimizations gaining a ~8% speed increase (@antiklesys)
- Modified `hf iclass tear` - now has a device side implementation also. (@antiklesys) (@iceman1001)
- Changed `hf iclass info` - now uses CSN values based checks (@antiklesys)
- Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001)
- Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001)
- Changed `hw tearoff` - the device side message is now debug log controlled (@iceman1001)
- Changed `pm3.sh` - Serial ports enumeration on Proxspace3.xx / MINGW environments, now using powershell.exe since wmic is deprecated (@iceman1001)
- Fixed and updated `hf iclass trbl` to correctly use the credit key when passed and show partial tearoff results (@antiklesys)
- Fixed `hf iclass legbrute` was not correctly parsin the index value
- Fixed `hf iclass legbrute` was not correctly parsing the index value
- Fixed `hf mf ekeyprn` - failed to download emulator memory due to wrong size calculation (@iceman1001)
- Fixed `hf mf fchk --mem` to actually use flash dict (@doegox)
- Fixed `make install` on OSX thanks DaveItsLong (@doegox)
- Added new standalone mode `HF_ST25_TEAROFF` to store/restore ST25TB tags with tearoff for counters (@seclabz)
- Added `hf_mfu_ultra.lua` script enables restoring dump to ULTRA/UL-5 tags and clearing previously written ULTRA tags (@mak-42)
- Fixed `hf mfu sim` to make persistent the counter increases in the emulator memory (@sup3rgiu)
## [Blue Ice.4.20142][2025-03-25]
- Added `des_talk.py` script for easier MIFARE DESFire handling (@trigat)

View file

@ -233,10 +233,8 @@ static void become_card(void) {
tag_response_info_t *canned;
uint32_t cuid;
uint32_t counters[3] = { 0 };
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
uint8_t pages;
SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, counters, tearings, &pages);
SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, &pages);
DbpString(_CYAN_("[@]") " Setup done - entering emulation loop");
int fromReaderLen;

View file

@ -379,7 +379,7 @@ void RunMod(void) {
BigBuf_free_keep_EM();
// tag type: 11 = ISO/IEC 14443-4 - javacard (JCOP)
if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) {
if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
DbpString(_RED_("Error initializing the emulation process!"));

View file

@ -268,7 +268,7 @@ void RunMod() {
BigBuf_free_keep_EM();
// 4 = ISO/IEC 14443-4 - javacard (JCOP)
if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) {
if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
DbpString(_RED_("Error initializing the emulation process!"));

View file

@ -118,8 +118,6 @@ void RunMod(void) {
uint8_t tagType = 10; // 10 = ST25TA IKEA Rothult
tag_response_info_t *responses;
uint32_t cuid = 0;
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
uint8_t pages = 0;
// command buffers
@ -193,7 +191,7 @@ void RunMod(void) {
memcpy(data, stuid, sizeof(stuid));
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) {
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");
@ -371,7 +369,7 @@ void RunMod(void) {
memcpy(data, stuid, sizeof(stuid));
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) {
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");

View file

@ -99,6 +99,7 @@ int tearoff_hook(void) {
if (g_tearoff_enabled) {
if (g_tearoff_delay_us == 0) {
Dbprintf(_RED_("No tear-off delay configured!"));
g_tearoff_enabled = false;
return PM3_SUCCESS; // SUCCESS = the hook didn't do anything
}
SpinDelayUsPrecision(g_tearoff_delay_us);
@ -2229,6 +2230,10 @@ static void PacketReceived(PacketCommandNG *packet) {
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
break;
}
case CMD_HF_ICLASS_TEARBL: {
iClass_TearBlock((iclass_tearblock_req_t *)packet->data.asBytes);
break;
}
#endif
#ifdef WITH_HFSNIFF

View file

@ -38,6 +38,7 @@
#include "iso15693.h"
#include "iclass_cmd.h" // iclass_card_select_t struct
#include "i2c.h" // i2c defines (SIM module access)
#include "printf.h"
uint8_t get_pagemap(const picopass_hdr_t *hdr) {
return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
@ -1244,7 +1245,6 @@ static bool iclass_send_cmd_with_retries(uint8_t *cmd, size_t cmdsize, uint8_t *
while (tries-- > 0) {
iclass_send_as_reader(cmd, cmdsize, start_time, eof_time, shallow_mod);
if (resp == NULL) {
return true;
}
@ -1582,8 +1582,9 @@ bool iclass_read_block(uint16_t blockno, uint8_t *data, uint32_t *start_time, ui
uint8_t c[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
AddCrc(c + 1, 1);
bool isOK = iclass_send_cmd_with_retries(c, sizeof(c), resp, sizeof(resp), 10, 2, start_time, ICLASS_READER_TIMEOUT_OTHERS, eof_time, shallow_mod);
if (isOK)
if (isOK) {
memcpy(data, resp, 8);
}
return isOK;
}
@ -1780,13 +1781,13 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac,
}
} else if (blockno == 3 || blockno == 4) {
// check response. Key updates always return 0xffffffffffffffff
uint8_t all_ff[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
if (memcmp(all_ff, resp, 8)) {
uint8_t all_ff[PICOPASS_BLOCK_SIZE] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
if (memcmp(all_ff, resp, PICOPASS_BLOCK_SIZE)) {
return false;
}
} else {
// check response. All other updates return unchanged data
if (memcmp(data, resp, 8)) {
if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) {
return false;
}
}
@ -1794,6 +1795,28 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac,
return true;
}
static bool iclass_writeblock_sp(uint8_t blockno, uint8_t *data, uint8_t *mac, bool shallow_mod, uint32_t *start_time, uint32_t *eof_time) {
// write command: cmd, 1 blockno, 8 data, 4 mac
uint8_t write[14] = { 0x80 | ICLASS_CMD_UPDATE, blockno };
uint8_t write_len = 14;
memcpy(write + 2, data, 8);
memcpy(write + 10, mac, 4);
uint8_t resp[10] = {0};
bool isOK = iclass_send_cmd_with_retries(write, write_len, resp, sizeof(resp), 10, 3, start_time, ICLASS_READER_TIMEOUT_UPDATE, eof_time, shallow_mod);
if (isOK == false) {
return false;
}
// check response. All other updates return unchanged data
if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) {
return false;
}
return true;
}
// turn off afterwards
void iClass_WriteBlock(uint8_t *msg) {
@ -1829,7 +1852,7 @@ void iClass_WriteBlock(uint8_t *msg) {
}
// new block data
memcpy(write + 2, payload->data, 8);
memcpy(write + 2, payload->data, PICOPASS_BLOCK_SIZE);
uint8_t pagemap = get_pagemap(&hdr);
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
@ -1847,7 +1870,7 @@ void iClass_WriteBlock(uint8_t *msg) {
// Secure tags uses MAC
uint8_t wb[9];
wb[0] = payload->req.blockno;
memcpy(wb + 1, payload->data, 8);
memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE);
if (payload->req.use_credit_key)
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
@ -2074,6 +2097,421 @@ out:
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
}
static void iclass_cmp_print(uint8_t *b1, uint8_t *b2, const char *header1, const char *header2) {
char line1[240] = {0};
char line2[240] = {0};
strcat(line1, header1);
strcat(line2, header2);
for (uint8_t i = 0; i < PICOPASS_BLOCK_SIZE; i++) {
int l1 = strlen(line1);
int l2 = strlen(line2);
uint8_t hi1 = NIBBLE_HIGH(b1[i]);
uint8_t low1 = NIBBLE_LOW(b1[i]);
uint8_t hi2 = NIBBLE_HIGH(b2[i]);
uint8_t low2 = NIBBLE_LOW(b2[i]);
if (hi1 != hi2) {
sprintf(line1 + l1, _RED_("%1X"), hi1);
sprintf(line2 + l2, _GREEN_("%1X"), hi2);
} else {
sprintf(line1 + l1, "%1X", hi1);
sprintf(line2 + l2, "%1X", hi2);
}
l1 = strlen(line1);
l2 = strlen(line2);
if (low1 != low2) {
sprintf(line1 + l1, _RED_("%1X"), low1);
sprintf(line2 + l2, _GREEN_("%1X"), low2);
} else {
sprintf(line1 + l1, "%1X", low1);
sprintf(line2 + l2, "%1X", low2);
}
}
DbpString(line1);
DbpString(line2);
}
void iClass_TearBlock(iclass_tearblock_req_t *msg) {
if (msg == NULL) {
reply_ng(CMD_HF_ICLASS_TEARBL, PM3_ESOFT, NULL, 0);
return;
}
// local variable copies
int tear_start = msg->tear_start;
int tear_end = msg->tear_end;
int tear_inc = msg->increment;
int tear_loop = msg->tear_loop;
int loop_count = 0;
uint32_t start_time = 0;
uint32_t eof_time = 0;
int isok = PM3_SUCCESS;
uint8_t data[8] = {0};
memcpy(data, msg->data, sizeof(data));
uint8_t mac[4] = {0};
memcpy(mac, msg->mac, sizeof(mac));
picopass_hdr_t hdr = {0};
iclass_auth_req_t req = {
.blockno = msg->req.blockno,
.do_auth = msg->req.do_auth,
.send_reply = msg->req.send_reply,
.shallow_mod = msg->req.shallow_mod,
.use_credit_key = msg->req.use_credit_key,
.use_elite = msg->req.use_elite,
.use_raw = msg->req.use_raw,
.use_replay = msg->req.use_replay
};
memcpy(req.key, msg->req.key, PICOPASS_BLOCK_SIZE);
LED_A_ON();
Iso15693InitReader();
// save old debug log level
int oldbg = g_dbglevel;
// no debug logging please
g_dbglevel = DBG_NONE;
// select
bool res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
if (res == false) {
DbpString(_RED_("Failed to select iClass tag"));
isok = PM3_ECARDEXCHANGE;
goto out;
}
// authenticate
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, mac);
if (res == false) {
DbpString(_RED_("Failed to authenticate with iClass tag"));
isok = PM3_ECARDEXCHANGE;
goto out;
}
uint8_t data_read_orig[PICOPASS_BLOCK_SIZE] = {0};
// read block
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = iclass_read_block(req.blockno, data_read_orig, &start_time, &eof_time, req.shallow_mod);
if (res == false) {
Dbprintf("Failed to read block %u", req.blockno);
isok = PM3_ECARDEXCHANGE;
goto out;
}
bool erase_phase = false;
bool read_ok = false;
// static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t ff_data[PICOPASS_BLOCK_SIZE] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
uint8_t data_read[PICOPASS_BLOCK_SIZE] = {0};
// create READ command
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, req.blockno, 0x00, 0x00};
AddCrc(cmd_read + 1, 1);
// create WRITE COMMAND and new block data
uint8_t cmd_write[14] = { 0x80 | ICLASS_CMD_UPDATE, req.blockno };
uint8_t cmd_write_len = 14;
memcpy(cmd_write + 2, data, PICOPASS_BLOCK_SIZE);
uint8_t pagemap = get_pagemap(&hdr);
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
// Unsecured tags uses CRC16, but don't include the UPDATE operation code
// byte0 = update op
// byte1 = block no
// byte2..9 = new block data
AddCrc(cmd_write + 1, 9);
cmd_write_len -= 2;
} else {
if (req.use_replay) {
memcpy(cmd_write + 10, mac, sizeof(mac));
} else {
// Secure tags uses MAC
uint8_t wb[9];
wb[0] = req.blockno;
memcpy(wb + 1, data, PICOPASS_BLOCK_SIZE);
if (req.use_credit_key)
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
else
doMAC_N(wb, sizeof(wb), hdr.key_d, mac);
memcpy(cmd_write + 10, mac, sizeof(mac));
}
}
// Main loop
while ((tear_start <= tear_end) && (read_ok == false)) {
if (BUTTON_PRESS() || data_available()) {
isok = PM3_EOPABORTED;
goto out;
}
// set tear off trigger
g_tearoff_enabled = true;
g_tearoff_delay_us = (tear_start & 0xFFFF);
if (tear_loop > 1) {
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us - " _YELLOW_("%3u") " iter", tear_start, tear_end, loop_count + 1);
} else {
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us", tear_start, tear_end);
}
// write block
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod);
tearoff_hook();
switch_off();
// start reading block
// reinit
Iso15693InitReader();
// select tag
res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
if (res == false) {
continue;
}
// skip authentication for config and e-purse blocks (1,2)
if (req.blockno > 2) {
// authenticate
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, NULL);
if (res == false) {
DbpString("Failed to authenticate after tear");
continue;
}
}
// read again and keep field on
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = iclass_read_block(req.blockno, data_read, &start_time, &eof_time, req.shallow_mod);
if (res == false) {
DbpString("Failed to read block after tear");
continue;
}
//
bool tear_success = true;
if (memcmp(data_read, data, PICOPASS_BLOCK_SIZE) != 0) {
tear_success = false;
}
if ((tear_success == false) &&
(memcmp(data_read, zeros, PICOPASS_BLOCK_SIZE) != 0) &&
(memcmp(data_read, data_read_orig, PICOPASS_BLOCK_SIZE) != 0)) {
// tearoff succeeded (partially)
if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 &&
memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) {
if(erase_phase == false){
DbpString("");
DbpString(_CYAN_("Erase phase hit... ALL ONES"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
}
erase_phase = true;
} else {
if (erase_phase) {
DbpString("");
DbpString(_MAGENTA_("Tearing! Write phase (post erase)"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
} else {
DbpString("");
DbpString(_CYAN_("Tearing! unknown phase"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
}
}
// shall we exit? well it depends on some things.
bool goto_out = false;
if (req.blockno == 2) {
if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 && memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) {
DbpString("");
Dbprintf("E-purse has been teared ( %s )", _GREEN_("ok"));
isok = PM3_SUCCESS;
goto_out = true;
}
}
if (req.blockno == 1) {
// if more OTP bits set..
if (data_read[1] > data_read_orig[1] ||
data_read[2] > data_read_orig[2]) {
// step 4 if bits changed attempt to write the new bits to the tag
if (data_read[7] == 0xBC) {
data_read[7] = 0xAC;
}
// prepare WRITE command
cmd_write_len = 14;
memcpy(cmd_write + 2, data_read, PICOPASS_BLOCK_SIZE);
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
// Unsecured tags uses CRC16, but don't include the UPDATE operation code
// byte0 = update op
// byte1 = block no
// byte2..9 = new block data
AddCrc(cmd_write + 1, 9);
cmd_write_len -= 2;
} else {
if (req.use_replay) {
memcpy(cmd_write + 10, mac, sizeof(mac));
} else {
// Secure tags uses MAC
uint8_t wb[9];
wb[0] = req.blockno;
memcpy(wb + 1, data_read, PICOPASS_BLOCK_SIZE);
if (req.use_credit_key)
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
else
doMAC_N(wb, sizeof(wb), hdr.key_d, mac);
memcpy(cmd_write + 10, mac, sizeof(mac));
}
}
// write block
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod);
uint16_t resp_len = 0;
uint8_t resp[ICLASS_BUFFER_SIZE] = {0};
res = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true, &resp_len);
if (res == PM3_SUCCESS && resp_len == 10) {
Dbprintf("Wrote to block");
}
switch_off();
DbpString("");
DbpString("More OTP bits got set!!!");
Iso15693InitReader();
// select tag, during which we read block1
res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
if (res) {
if (memcmp(&hdr.conf, cmd_write + 2, PICOPASS_BLOCK_SIZE) == 0) {
Dbprintf("Stabilize the bits ( "_GREEN_("ok") " )");
} else {
Dbprintf("Stabilize the bits ( "_RED_("failed") " )");
}
}
isok = PM3_SUCCESS;
goto_out = true;
}
if (data_read[0] != data_read_orig[0]) {
DbpString("");
Dbprintf("Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]);
isok = PM3_SUCCESS;
goto_out = true;
}
if (data_read[7] != data_read_orig[7]) {
DbpString("");
Dbprintf("Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]);
const char *flag_names[8] = {
"RA",
"Fprod0",
"Fprod1",
"Crypt0 (*1)",
"Crypt1 (*0)",
"Coding0",
"Coding1",
"Fpers (*1)"
};
Dbprintf(_YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed");
Dbprintf("---------------------------------------");
for (int i = 7; i >= 0; --i) {
int bit1 = (data_read_orig[7] >> i) & 1;
int bit2 = (data_read[7] >> i) & 1;
Dbprintf("%-11s %-10d %-10d", flag_names[i], bit1, bit2);
}
isok = PM3_SUCCESS;
goto_out = true;
}
}
if (goto_out) {
goto out;
}
}
// tearoff succeeded with expected values, which is unlikely
if (tear_success) {
read_ok = true;
tear_success = true;
DbpString("");
DbpString("tear success (expected values)!");
}
loop_count++;
// increase tear off delay
if (loop_count == tear_loop) {
tear_start += tear_inc;
loop_count = 0;
}
}
out:
switch_off();
// reset tear off trigger
g_tearoff_enabled = false;
// restore debug message levels
g_dbglevel = oldbg;
if (msg->req.send_reply) {
reply_ng(CMD_HF_ICLASS_TEARBL, isok, NULL, 0);
}
}
void iClass_Restore(iclass_restore_req_t *msg) {
// sanitation
@ -2204,6 +2642,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
int bits_found = -1;
bool recovered = false;
bool completed = false;
bool interrupted = false;
uint8_t div_key2[8] = {0};
uint32_t eof_time = 0;
uint32_t start_time = 0;
@ -2225,7 +2664,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
};
LED_A_ON();
DbpString(_RED_("Interrupting this process will render the card unusable!"));
DbpString(_RED_("Interrupting this process may render the card unusable!"));
memcpy(div_key2, msg->nfa, 8);
//START LOOP
@ -2243,8 +2682,26 @@ void iClass_Recover(iclass_recover_req_t *msg) {
uint8_t mac2[4] = {0};
picopass_hdr_t hdr = {0};
bool res = false;
int status_message = 0;
while (!card_select || !card_auth) {
if (BUTTON_PRESS() || loops > msg->loop) {
if(loops > msg->loop){
completed = true;
}else{
interrupted = true;
}
goto out;
}
if (msg->test) {
Dbprintf(_YELLOW_("*Cycled Reader*") " TEST Index - Loops: "_YELLOW_("%3d / %3d") " *", loops, msg->loop);
}else if (msg->debug){
Dbprintf(_YELLOW_("*Cycled Reader*") " Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " *", index, loops, msg->loop);
}else{
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Index: "_CYAN_("%3d")" Loops: "_YELLOW_("%3d / %3d")" ", index, loops, msg->loop);
}
Iso15693InitReader(); //has to be at the top as it starts tracing
if (!msg->debug) {
set_tracing(false); //disable tracing to prevent crashes - set to true for debugging
@ -2253,18 +2710,11 @@ void iClass_Recover(iclass_recover_req_t *msg) {
clear_trace(); //if we're debugging better to clear the trace but do it only on the first loop
}
}
if (msg->test) {
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- TEST Index - Loops: "_YELLOW_("%3d / %3d") " --------------*", loops, msg->loop);
} else {
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " --------------*", index, loops, msg->loop);
}
//Step0 Card Select Routine
eof_time = 0; //reset eof time
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
if (res == false) {
DbpString(_RED_("Unable to select card after reader cycle! Retrying..."));
} else {
DbpString(_GREEN_("Card selected successfully!"));
if (res) {
status_message = 1; //card select successful
card_select = true;
}
@ -2273,10 +2723,8 @@ void iClass_Recover(iclass_recover_req_t *msg) {
memcpy(original_mac, msg->req.key, 8);
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (res == false) {
DbpString(_RED_("Unable to authenticate on AA1 using macs! Retrying..."));
} else {
DbpString(_GREEN_("AA1 authentication with macs successful!"));
if (res) {
status_message = 2; //authentication with AA1 macs successful
card_auth = true;
}
}
@ -2285,6 +2733,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
switch_off();
}
if (reinit_tentatives == 5) {
DbpString("");
DbpString(_RED_("Unable to select or authenticate with card multiple times! Stopping."));
goto out;
}
@ -2292,23 +2741,22 @@ void iClass_Recover(iclass_recover_req_t *msg) {
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
uint8_t blockno = 24;
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
AddCrc(cmd_read + 1, 1);
int priv_esc_tries = 0;
bool priv_esc = false;
while (!priv_esc) {
//The privilege escalation is done with a readcheck and not just a normal read!
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
// expect a 8-byte response here
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res2 != PM3_SUCCESS || resp_len != 8) {
DbpString(_YELLOW_("Privilege Escalation -> ")_RED_("Read failed! Trying again..."));
priv_esc_tries++;
} else {
DbpString(_YELLOW_("Privilege Escalation -> ")_GREEN_("Response OK!"));
status_message = 3; //privilege escalation successful
priv_esc = true;
}
if (priv_esc_tries == 5) {
DbpString("");
DbpString(_RED_("Unable to complete privilege escalation! Stopping."));
goto out;
}
@ -2327,37 +2775,37 @@ void iClass_Recover(iclass_recover_req_t *msg) {
wb[0] = blockno;
memcpy(wb + 1, genkeyblock, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
bool use_mac = true;
bool written = false;
bool write_error = false;
while (written == false && write_error == false) {
//Step5 Perform Write
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
DbpString("Wrote key: ");
Dbhexdump(8, genkeyblock, false);
if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) {
status_message = 4; //wrote new key on the card - unverified
}
//Reset cypher state
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//try to authenticate with the original mac to verify the write happened
memcpy(msg->req.key, original_mac, 8);
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (msg->test) {
if (res != true) {
DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***"));
goto out;
} else {
if (res) {
DbpString("");
DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***"));
completed = true;
goto out;
} else {
DbpString("");
DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***"));
goto out;
}
} else {
if (res != true) {
DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!"));
written = true;
if (res) {
write_error = true; //failed to update the key, the card's key is the original one
} else {
DbpString("Write Operation : "_RED_("FAILED! Card Key is the Original. Retrying..."));
write_error = true;
status_message = 5; //verified the card key was updated to the new one
written = true;
}
}
}
@ -2365,16 +2813,14 @@ void iClass_Recover(iclass_recover_req_t *msg) {
if (!write_error) {
//Step6 Perform 8 authentication attempts + 1 to verify if we found the weak key
for (int i = 0; i < 8 ; ++i) {
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//need to craft the authentication payload accordingly
memcpy(msg->req.key, iclass_mac_table[i], 8);
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter
if (res == true) {
bits_found = i;
DbpString(_RED_("--------------------------------------------------------"));
Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found);
DbpString(_RED_("--------------------------------------------------------"));
recovered = true;
}
}
@ -2384,37 +2830,36 @@ void iClass_Recover(iclass_recover_req_t *msg) {
uint8_t revert_retries = 0;
while (!reverted) {
//Regain privilege escalation with a readcheck
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
// TODO: check result
GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
DbpString(_YELLOW_("Attempting to restore the original key. "));
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
DbpString("Restore of Original Key "_GREEN_("successful."));
} else {
DbpString("Restore of Original Key " _RED_("failed."));
//GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) {
status_message = 6; //restore of original key successful but unverified
}
DbpString(_YELLOW_("Verifying Key Restore..."));
//Do a readcheck first to reset the cypher state
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
// TODO: check result
GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//need to craft the authentication payload accordingly
memcpy(msg->req.key, original_mac, 8);
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (res == true) {
DbpString("Restore of Original Key "_GREEN_("VERIFIED! Card is usable again."));
status_message = 7; //restore of original key verified - card usable again
reverted = true;
if (recovered) {
goto restore;
}
} else {
DbpString("Restore of Original Key "_RED_("VERIFICATION FAILED! Trying again..."));
}
revert_retries++;
if (revert_retries >= 7) { //must always be an odd number!
DbpString("");
DbpString(_CYAN_("Last Written Key: "));
Dbhexdump(8, genkeyblock, false);
Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries);
goto out;
}
@ -2422,11 +2867,35 @@ void iClass_Recover(iclass_recover_req_t *msg) {
}
if (loops >= msg->loop) {
completed = true;
goto out;
if(msg->debug){
if(status_message >= 1){
DbpString("");
DbpString("Card Select:............."_GREEN_("Ok!"));
}
if(status_message >= 2){
DbpString("AA1 macs authentication:."_GREEN_("Ok!"));
}
if(status_message >= 3){
DbpString("Privilege Escalation:...."_GREEN_("Ok!"));
}
if(status_message >= 4){
DbpString("Wrote key: ");
Dbhexdump(8, genkeyblock, false);
}
if(status_message >= 5){
DbpString("Key Update:.............."_GREEN_("Verified!"));
}
if(status_message >= 6){
DbpString("Original Key Restore:...."_GREEN_("Ok!"));
}
if(status_message >= 7){
DbpString("Original Key Restore:...."_GREEN_("Verified!"));
}
}
if (!write_error) { //if there was a write error, re-run the loop for the same key index
if (write_error && (msg->debug || msg->test)) { //if there was a write error, re-run the loop for the same key index
DbpString("Loop Error: "_RED_("Repeating Loop!"));
}else{
loops++;
index++;
}
@ -2442,6 +2911,10 @@ restore:
partialkey[i] = genkeyblock[i] ^ bits_found;
}
//Print the bits decimal value
DbpString("");
DbpString(_RED_("--------------------------------------------------------"));
Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found);
//Print the 24 bits found from k1
DbpString(_RED_("--------------------------------------------------------"));
DbpString(_RED_("SUCCESS! Raw Key Partial Bytes: "));
@ -2456,6 +2929,8 @@ out:
switch_off();
if (completed) {
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EINVARG, NULL, 0);
} else if (interrupted){
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EOPABORTED, NULL, 0);
} else {
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
}

View file

@ -72,4 +72,5 @@ uint8_t get_pagemap(const picopass_hdr_t *hdr);
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
void iClass_Recover(iclass_recover_req_t *msg);
void iClass_TearBlock(iclass_tearblock_req_t *msg);
#endif

View file

@ -1184,7 +1184,7 @@ bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages) {
uint32_t *cuid, uint8_t *pages) {
uint8_t sak = 0;
// The first response contains the ATQA (note: bytes are transmitted in reverse order).
static uint8_t rATQA[2] = { 0x00 };
@ -1231,14 +1231,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
*pages = MAX(mfu_header->pages, 15);
// counters and tearing flags
// tearing flags
// for old dumps with all zero headers, we need to set default values.
for (uint8_t i = 0; i < 3; i++) {
counters[i] = le24toh(mfu_header->counter_tearing[i]);
if (mfu_header->counter_tearing[i][3] != 0x00) {
tearings[i] = mfu_header->counter_tearing[i][3];
if (mfu_header->counter_tearing[i][3] == 0x00) {
mfu_header->counter_tearing[i][3] = 0xBD;
}
}
@ -1286,14 +1283,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
*pages = MAX(mfu_header->pages, 19);
// counters and tearing flags
// tearing flags
// for old dumps with all zero headers, we need to set default values.
for (uint8_t i = 0; i < 3; i++) {
counters[i] = le24toh(mfu_header->counter_tearing[i]);
if (mfu_header->counter_tearing[i][3] != 0x00) {
tearings[i] = mfu_header->counter_tearing[i][3];
if (mfu_header->counter_tearing[i][3] == 0x00) {
mfu_header->counter_tearing[i][3] = 0xBD;
}
}
@ -1539,8 +1533,6 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
tag_response_info_t *responses;
uint32_t cuid = 0;
uint32_t nonce = 0;
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
uint8_t pages = 0;
// Here, we collect CUID, block1, keytype1, NT1, NR1, AR1, CUID, block2, keytyp2, NT2, NR2, AR2
@ -1584,12 +1576,22 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
.modulation_n = 0
};
if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) {
if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, &pages) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
return;
}
mfu_dump_t *mfu_em_dump = NULL;
if (tagType == 2 || tagType == 7) {
mfu_em_dump = (mfu_dump_t *)BigBuf_get_EM_addr();
if (!mfu_em_dump) {
if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: Failed to get EM address for MFU/NTAG operations.");
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0);
return;
}
}
// We need to listen to the high-frequency, peak-detected path.
iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN);
@ -1870,8 +1872,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
// send NACK 0x0 == invalid argument
EmSend4bit(CARD_NACK_IV);
} else {
uint8_t cmd[] = {0x00, 0x00, 0x00, 0x14, 0xa5};
htole24(counters[index], cmd);
uint8_t cmd[] = {0, 0, 0, 0x14, 0xa5};
memcpy(cmd, mfu_em_dump->counter_tearing[index], 3);
AddCrc14A(cmd, sizeof(cmd) - 2);
EmSendCmd(cmd, sizeof(cmd));
}
@ -1882,13 +1884,16 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
// send NACK 0x0 == invalid argument
EmSend4bit(CARD_NACK_IV);
} else {
uint32_t val = le24toh(receivedCmd + 2) + counters[index];
uint32_t val = le24toh(mfu_em_dump->counter_tearing[index]); // get current counter value
val += le24toh(receivedCmd + 2); // increment in
// if new value + old value is bigger 24bits, fail
if (val > 0xFFFFFF) {
// send NACK 0x4 == counter overflow
EmSend4bit(CARD_NACK_NA);
} else {
counters[index] = val;
htole24(val, mfu_em_dump->counter_tearing[index]);
// send ACK
EmSend4bit(CARD_ACK);
}
@ -1902,7 +1907,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
EmSend4bit(CARD_NACK_IV);
} else {
uint8_t cmd[3] = {0, 0, 0};
cmd[0] = tearings[index];
cmd[0] = mfu_em_dump->counter_tearing[index][3];
AddCrc14A(cmd, sizeof(cmd) - 2);
EmSendCmd(cmd, sizeof(cmd));
}
@ -4091,8 +4096,6 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
uint8_t *getdata_response, size_t getdata_response_len) {
tag_response_info_t *responses;
uint32_t cuid = 0;
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
uint8_t pages = 0;
// command buffers
@ -4133,7 +4136,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
.modulation_n = 0
};
if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) {
if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, &pages) == false) {
BigBuf_free_keep_EM();
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
return;
@ -4276,6 +4279,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
dynamic_response_info.response_n = 3 + offset;
}
}
break;
}
break;

View file

@ -152,7 +152,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages);
uint32_t *cuid, uint8_t *pages);
bool GetIso14443aCommandFromReader(uint8_t *received, uint16_t received_maxlen, uint8_t *par, int *len);
void iso14443a_antifuzz(uint32_t flags);

View file

@ -2135,6 +2135,9 @@ static int iso14443b_select_picopass_card(picopass_hdr_t *hdr) {
static uint8_t act_all[] = { ICLASS_CMD_ACTALL };
static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY };
static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 };
// ICLASS_CMD_SELECT 0x81 tells ISO14443b/BPSK coding/106 kbits/s
// ICLASS_CMD_SELECT 0x41 tells ISO14443b/BPSK coding/423 kbits/s
uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64};
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 };

View file

@ -1827,14 +1827,17 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
const char *product_type_str = "";
const char *major_product_version_str = "";
const char *storage_size_str = "";
if (version_hw_available) {
switch (version_hw->product_type & 0x0F) {
case 0x1:
case 0x1: {
product_type_str = "MIFARE DESFire";
// special cases, override product_type_str when needed
if (version_hw->product_type == 0x91) {
product_type_str = "Apple Wallet DESFire Applet";
}
// general rule
switch (version_hw->major_product_version & 0x0F) {
case 0x01:
@ -1847,6 +1850,7 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
major_product_version_str = "EV3";
break;
}
// special cases, override major_product_version_str when needed
switch (version_hw->major_product_version) {
case 0x00:
@ -1860,7 +1864,8 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
break;
}
break;
case 0x2:
}
case 0x2: {
product_type_str = "MIFARE Plus";
switch (version_hw->major_product_version) {
case 0x11:
@ -1870,15 +1875,23 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
major_product_version_str = "EV2";
break;
default:
major_product_version_str = "Unknown";
major_product_version_str = "n/a";
}
break;
case 0x3:
}
case 0x3: {
product_type_str = "MIFARE Ultralight";
switch (version_hw->major_product_version) {
case 0x01:
case 0x01: {
major_product_version_str = "EV1";
if (version_hw->storage_size == 0x0B) {
storage_size_str = "48b";
} else if (version_hw->storage_size == 0x0E) {
storage_size_str = "128b";
}
break;
}
case 0x02:
major_product_version_str = "Nano";
break;
@ -1886,10 +1899,11 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
major_product_version_str = "AES";
break;
default:
major_product_version_str = "Unknown";
major_product_version_str = "n/a";
}
break;
case 0x4:
}
case 0x4: {
product_type_str = "NTAG";
switch (version_hw->major_product_version) {
case 0x01:
@ -1909,37 +1923,66 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
major_product_version_str = "4xx";
break;
default:
major_product_version_str = "Unknown";
major_product_version_str = "n/a";
}
break;
case 0x7:
}
case 0x7: {
product_type_str = "NTAG I2C";
break;
case 0x8:
}
case 0x8: {
product_type_str = "MIFARE DESFire Light";
break;
case 0x9:
}
case 0x9: {
product_type_str = "MIFARE Hospitality";
switch (version_hw->major_product_version) {
case 0x01:
major_product_version_str = "AES";
break;
default:
major_product_version_str = "Unknown";
major_product_version_str = "n/a";
}
break;
default:
}
default: {
product_type_str = "Unknown NXP tag";
break;
}
}
uint32_t size = 1 << (version_hw->storage_size >> 1);
static char size_str[16];
if (size < 1024) {
snprintf(size_str, sizeof(size_str), "%s%uB", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size);
} else {
snprintf(size_str, sizeof(size_str), "%s%uK", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size / 1024);
if (storage_size_str == NULL) {
static char size_str[16];
uint16_t usize = 1 << ((version_hw->storage_size >> 1) + 1);
uint16_t lsize = 1 << (version_hw->storage_size >> 1);
// is LSB set?
if ((version_hw->storage_size & 0x01) == 1) {
// if set, its a range between upper size and lower size
if (lsize < 1024) {
snprintf(size_str, sizeof(size_str), "%u - %uB", usize, lsize);
} else {
snprintf(size_str, sizeof(size_str), "%u - %uK", (usize / 1024), (lsize / 1024));
}
} else {
// if not set, it's lower size
if (lsize < 1024) {
snprintf(size_str, sizeof(size_str), "%uB", lsize);
} else {
snprintf(size_str, sizeof(size_str), "%uK", lsize / 1024);
}
}
storage_size_str = size_str;
}
storage_size_str = size_str;
}
char tag_info[128];
if ((sak & 0x44) == 0x40) {
@ -1951,25 +1994,36 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
}
type |= MTISO18092;
}
if ((sak & 0x02) == 0x00) { // SAK b2=0
if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=1
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19
printTag("MIFARE Classic 2K");
type |= MTCLASSIC;
} else { // SAK b2=0 b4=1 b5=1 b1=0
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38
printTag("SmartMX with MIFARE Classic 4K");
type |= MTCLASSIC;
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0
if (select_status == 4) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS
if (version_hw_available) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS GetVersion
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
printTag(tag_info);
type |= MTPLUS;
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion
if (ats_hist_len > 0) {
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
printTag("MIFARE Plus S 4K in SL1");
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
@ -1988,7 +2042,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
}
}
}
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18
if ((atqa & 0x0040) == 0x0040) {
printTag("MIFARE Classic 4K CL2");
} else {
@ -2000,28 +2056,38 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
}
} else { // SAK b2=0 b4=1 b5=0
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09
if ((atqa & 0x0040) == 0x0040) {
printTag("MIFARE Mini 0.3K CL2");
} else {
printTag("MIFARE Mini 0.3K");
}
type |= MTMINI;
} else { // SAK b2=0 b4=1 b5=0 b1=0
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28
printTag("SmartMX with MIFARE Classic 1K");
printTag("FM1208-10 with MIFARE Classic 1K");
printTag("FM1216-137 with MIFARE Classic 1K");
type |= MTCLASSIC;
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0
if (select_status == 4) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS
if (version_hw_available) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS GetVersion
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
printTag(tag_info);
type |= MTPLUS;
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion
if (ats_hist_len > 0) {
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
printTag("MIFARE Plus S 2K in SL1");
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
@ -2030,7 +2096,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
printTag("Unrecognized MIFARE Plus??");
}
type |= MTPLUS;
} else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) {
if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
printTag("MIFARE Plus SE 1K 17pF");
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
@ -2039,7 +2107,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
printTag("Unrecognized MIFARE Plus SE??");
}
type |= MTPLUS;
} else {
if ((atqa & 0x0040) == 0x0040) {
printTag("MIFARE Classic 1K CL2 with ATS!");
} else {
@ -2060,8 +2130,11 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
}
}
}
} else { // SAK b2=0 b4=0
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11
printTag("MIFARE Plus 4K in SL2");
type |= MTPLUS;
@ -2069,33 +2142,48 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
printTag("MIFARE Plus 2K in SL2");
type |= MTPLUS;
}
} else { // SAK b2=0 b4=0 b5=0
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1
printTag("TNP3xxx (TagNPlay, Activision Game Appliance)");
type |= MTCLASSIC;
} else { // SAK b2=0 b4=0 b5=0 b1=0
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1, SAK=0x20
if (select_status == 1) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS
if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion
if ((version_hw->product_type & 0x7F) == 0x02) {
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL0/SL3", product_type_str, major_product_version_str, storage_size_str);
type |= MTPLUS;
} else if (((version_hw->product_type & 0x7F) == 0x01) ||
(version_hw->product_type == 0x08) ||
(version_hw->product_type == 0x91)) {
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
type |= MTDESFIRE;
} else if (version_hw->product_type == 0x04) {
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
type |= (MTDESFIRE | MT424);
} else {
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
}
printTag(tag_info);
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion
if (ats_hist_len > 0) {
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) {
if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
if ((atqa & 0xFF0F) == 0x0004) {
printTag("MIFARE Plus S 2K in SL0/SL3");
} else if ((atqa & 0xFF0F) == 0x0002) {
@ -2103,21 +2191,28 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
} else {
printTag("Unrecognized MIFARE Plus??");
}
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
printTag("MIFARE Plus X 2K/4K in SL0/SL3");
} else if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
printTag("MIFARE Plus SE 1K 17pF");
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
printTag("MIFARE Plus SE 1K 70pF");
} else {
printTag("Unrecognized MIFARE Plus??");
printTag("Unknown MIFARE Plus");
}
type |= MTPLUS;
} else {
if ((atqa == 0x0001) || (atqa == 0x0004)) {
printTag("HID SEOS (smartmx / javacard)");
type |= HID_SEOS;
}
if (atqa == 0x0004) {
printTag("EMV");
type |= MTEMV;
@ -2128,11 +2223,15 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
} else {
printTag("Unknown tag claims to support RATS in SAK but does not...");
}
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0, SAK=0x00
if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 GetVersion
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
printTag(tag_info);
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion
int status = mfuc_test_authentication_support();
if (status == PM3_SUCCESS) {
// TODO: read page 2/3, then ??
@ -2141,6 +2240,7 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
} else {
printTag("MIFARE Ultralight");
}
}
type |= MTULTRALIGHT;
}
@ -2148,20 +2248,25 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
}
}
} else { // SAK b2=1
if (sak == 0x0A) {
if (atqa == 0x0003) {
// Uses Shanghai algo
printTag("FM11RF005SH (FUDAN Shanghai Metro)");
type |= MTFUDAN;
} else if (atqa == 0x0005) {
printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)");
type |= MTFUDAN;
}
} else if (sak == 0x53) {
printTag("FM11RF08SH (FUDAN)");
type |= MTFUDAN;
}
}
if (type == MTNONE) {
PrintAndLogEx(WARNING, " failed to fingerprint");
}
@ -2803,8 +2908,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
}
DropField();
if (verbose == false && found)
if (verbose == false && found) {
PrintAndLogEx(INFO, "----------------------------------------------------");
}
}
}
@ -2814,7 +2920,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
PrintAndLogEx(INFO, "proprietary iso18092 card found");
} else {
PrintAndLogEx(INFO, "proprietary non iso14443-4 card found, RATS not supported");
PrintAndLogEx(INFO, "");
PrintAndLogEx(INFO, "Proprietary non iso14443-4 card found");
PrintAndLogEx(INFO, "RATS not supported");
if ((card.sak & 0x20) == 0x20) {
PrintAndLogEx(INFO, "--> SAK incorrectly claims that card supports RATS <--");
}
@ -2829,7 +2937,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
return PM3_EFAILED;
}
PrintAndLogEx(INFO, "");
// PrintAndLogEx(INFO, "");
uint16_t isMagic = 0;

View file

@ -1411,7 +1411,7 @@ static bool HF14B_ask_ct_reader(bool verbose) {
return false;
}
bool HF14B_picopass_reader(bool verbose, bool info) {
static bool HF14B_picopass_reader(bool verbose) {
iso14b_raw_cmd_t packet = {
.flags = (ISO14B_CONNECT | ISO14B_SELECT_PICOPASS | ISO14B_DISCONNECT),
@ -1437,10 +1437,8 @@ bool HF14B_picopass_reader(bool verbose, bool info) {
return false;
}
memcpy(card, resp.data.asBytes, sizeof(picopass_hdr_t));
if (info) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
free(card);
return true;
}
@ -3038,7 +3036,6 @@ int infoHF14B(bool verbose, bool do_aid_search) {
// get and print general info about all known 14b chips
int readHF14B(bool loop, bool verbose, bool read_plot) {
bool found = false;
bool info = true;
int res = PM3_SUCCESS;
do {
found = false;
@ -3054,7 +3051,7 @@ int readHF14B(bool loop, bool verbose, bool read_plot) {
goto plot;
// Picopass
found |= HF14B_picopass_reader(verbose, info);
found |= HF14B_picopass_reader(verbose);
if (found)
goto plot;

View file

@ -31,6 +31,5 @@ int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card);
int infoHF14B(bool verbose, bool do_aid_search);
int readHF14B(bool loop, bool verbose, bool read_plot);
bool HF14B_picopass_reader(bool verbose, bool info);
#endif

View file

@ -40,19 +40,21 @@
#include "crypto/asn1utils.h" // ASN1 decoder
#include "preferences.h"
#include "generator.h"
#include "cmdhf14b.h"
#include "cmdhw.h"
#include "hidsio.h"
#define ICLASS_DEBIT_KEYTYPE ( 0x88 )
#define ICLASS_CREDIT_KEYTYPE ( 0x18 )
#define NUM_CSNS 9
#define MAC_ITEM_SIZE 24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes
#define ICLASS_KEYS_MAX 8
#define ICLASS_AUTH_RETRY 10
#define ICLASS_CFG_BLK_SR_BIT 0xA0 // indicates SIO present when set in block6[0] (legacy tags)
#define ICLASS_DECRYPTION_BIN "iclass_decryptionkey.bin"
#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic"
#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic"
#define ICLASS_DECRYPTION_BIN "iclass_decryptionkey.bin"
#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic"
#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic"
static void print_picopass_info(const picopass_hdr_t *hdr);
void print_picopass_header(const picopass_hdr_t *hdr);
@ -2804,10 +2806,10 @@ static int CmdHFiClass_ReadBlock(const char *Cmd) {
int blockno = arg_get_int_def(ctx, 3, 0);
uint8_t keyType = 0x88; //debit key
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
if (arg_get_lit(ctx, 4)) {
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
keyType = 0x18; //credit key
keyType = ICLASS_CREDIT_KEYTYPE;
}
bool elite = arg_get_lit(ctx, 5);
@ -2958,7 +2960,8 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
CLIParserInit(&ctx, "hf iclass tear",
"Tear off an iCLASS tag block\n"
"e-purse usually 300-500us to trigger the erase phase\n"
"also seen 1800-2100us on some cards\n",
"also seen 1800-2100us on some cards\n"
"Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`\n",
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600\n"
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600\n"
"hf iclass tear --blk 2 -d fdffffffffffffff --ki 1 --credit -s 400 -e 500"
@ -2982,6 +2985,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
arg_int0("e", NULL, "<dec>", "tearoff delay end (in us) must be a higher value than the start delay"),
arg_int0(NULL, "loop", "<dec>", "number of times to loop per tearoff time"),
arg_int0(NULL, "sleep", "<ms>", "Sleep between each tear"),
arg_lit0(NULL, "arm", "Runs the commands on device side and tries to stabilize tears"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@ -3014,6 +3018,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
int tearoff_end = arg_get_int_def(ctx, 14, tearoff_start + tearoff_increment + 500);
int tearoff_loop = arg_get_int_def(ctx, 15, 1);
int tearoff_sleep = arg_get_int_def(ctx, 16, 0);
bool run_on_device = arg_get_lit(ctx, 17);
CLIParserFree(ctx);
@ -3078,13 +3083,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
}
int loop_count = 0;
int isok = 0;
int isok = PM3_SUCCESS;
bool read_ok = false;
uint8_t keyType = 0x88; // debit key
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
if (use_credit_key) {
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
keyType = 0x18; // credit key
keyType = ICLASS_CREDIT_KEYTYPE;
}
if (auth == false) {
@ -3130,6 +3134,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
return PM3_ESOFT;
}
if (memcmp(r->header.hdr.csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
PrintAndLogEx(SUCCESS, "CSN................... %s ( new silicon )", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
} else {
PrintAndLogEx(SUCCESS, "CSN................... %s ( old silicon )", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
}
picopass_hdr_t *hdr = &r->header.hdr;
uint8_t pagemap = get_pagemap(hdr);
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
@ -3138,13 +3148,13 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
}
if (pagemap == 0x0) {
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled"));
goto out;
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled"));
goto out;
}
bool read_auth = auth;
// perform initial read here, repeat if failed or 00s
bool read_auth = auth;
uint8_t data_read_orig[8] = {0};
uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
bool first_read = false;
@ -3180,47 +3190,58 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
// clear trace log
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
PrintAndLogEx(INFO, "---------------------------------------");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
// Main loop
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
if (run_on_device) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard.");
isok = PM3_EOPABORTED;
goto out;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "---------------------------------------");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
// set tear off trigger
clearCommandBuffer();
tearoff_params_t params = {
.delay_us = (tearoff_start & 0xFFFF),
.on = true,
.off = false
iclass_tearblock_req_t payload = {
.req.use_raw = rawkey,
.req.use_elite = elite,
.req.use_credit_key = use_credit_key,
.req.use_replay = use_replay,
.req.blockno = blockno,
.req.send_reply = true,
.req.do_auth = auth,
.req.shallow_mod = shallow_mod,
.tear_start = tearoff_start,
.tear_end = tearoff_end,
.increment = tearoff_increment,
.tear_loop = tearoff_loop,
};
memcpy(payload.req.key, key, PICOPASS_BLOCK_SIZE);
memcpy(payload.data, data, sizeof(payload.data));
memcpy(payload.mac, mac, sizeof(payload.mac));
int res = handle_tearoff(&params, verbose);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Failed to configure tear off");
isok = PM3_ESOFT;
goto out;
clearCommandBuffer();
SendCommandNG(CMD_HF_ICLASS_TEARBL, (uint8_t *)&payload, sizeof(payload));
if (WaitForResponseTimeout(CMD_HF_ICLASS_TEARBL, &resp, 1000)) {
if (resp.status == PM3_EOPABORTED) {
PrintAndLogEx(DEBUG, "Button pressed, user aborted");
isok = resp.status;
}
}
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" / "_YELLOW_("%d")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count+1, tearoff_loop);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Done!");
PrintAndLogEx(NORMAL, "");
clearCommandBuffer();
return isok;
// write block - don't check the return value. As a tear-off occurred, the write failed.
iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
} else {
// read the data back
uint8_t data_read[8] = {0};
first_read = false;
reread = false;
bool decrease = false;
while (first_read == false) {
PrintAndLogEx(INFO, "---------------------------------------");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
// Main loop
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard.");
@ -3228,100 +3249,205 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
goto out;
}
if (blockno == 1) {
read_auth = false;
// set tear off trigger
clearCommandBuffer();
tearoff_params_t params = {
.delay_us = (tearoff_start & 0xFFFF),
.on = true,
.off = false
};
int res = handle_tearoff(&params, verbose);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Failed to configure tear off");
isok = PM3_ESOFT;
goto out;
}
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false);
if (res == PM3_SUCCESS && !reread) {
if (memcmp(data_read, zeros, 8) == 0) {
reread = true;
} else {
if (tearoff_loop > 1) {
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" iter", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1);
} else {
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF));
}
// write block - don't check the return value. As a tear-off occurred, the write failed.
// when tear off is enabled, the return code will always be PM3_ETEAROFF
iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod);
// read the data back
uint8_t data_read[8] = {0};
first_read = false;
reread = false;
bool decrease = false;
int readcount = 0;
while (first_read == false) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard.");
isok = PM3_EOPABORTED;
goto out;
}
// skip authentication for config and e-purse blocks (1,2)
if (blockno < 3) {
read_auth = false;
}
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false);
if (res == PM3_SUCCESS && !reread) {
if (memcmp(data_read, zeros, 8) == 0) {
reread = true;
} else {
first_read = true;
reread = false;
}
} else if (res == PM3_SUCCESS && reread) {
first_read = true;
reread = false;
} else if (res != PM3_SUCCESS) {
decrease = true;
}
} else if (res == PM3_SUCCESS && reread) {
first_read = true;
reread = false;
} else if (res != PM3_SUCCESS) {
decrease = true;
readcount++;
}
}
// if there was an error reading repeat the tearoff with the same delay
if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) {
tearoff_start -= tearoff_increment;
}
if (readcount > 1) {
PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount);
}
bool tear_success = true;
bool expected_values = true;
// if there was an error reading repeat the tearoff with the same delay
if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) {
tearoff_start -= tearoff_increment;
if (verbose) {
PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start);
}
}
if (memcmp(data_read, data, 8) != 0) {
tear_success = false;
}
bool tear_success = true;
bool expected_values = true;
if ((tear_success == false) && (memcmp(data_read, zeros, 8) != 0) && (memcmp(data_read, data_read_orig, 8) != 0)) {
if (memcmp(data_read, data, 8) != 0) {
tear_success = false;
}
// tearoff succeeded (partially)
if ((tear_success == false) &&
(memcmp(data_read, zeros, 8) != 0) &&
(memcmp(data_read, data_read_orig, 8) != 0)) {
expected_values = false;
// tearoff succeeded (partially)
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
erase_phase = true;
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
} else {
expected_values = false;
if (erase_phase) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
if (memcmp(data_read, ff_data, 8) == 0 &&
memcmp(data_read_orig, ff_data, 8) != 0) {
if (erase_phase == false){
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
}
erase_phase = true;
} else {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
}
}
if (blockno == 1) {
if (data_read[0] != data_read_orig[0]) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Application limit changed, from %u to %u", data_read_orig[0], data_read[0]);
isok = PM3_SUCCESS;
goto out;
if (erase_phase) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
} else {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase"));
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
}
}
if (data_read[7] != data_read_orig[7]) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Fuse changed, from %02x to %02x", data_read_orig[7], data_read[7]);
isok = PM3_SUCCESS;
bool goto_out = false;
if (blockno == 2) {
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok"));
PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`");
PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`");
isok = PM3_SUCCESS;
goto_out = true;
}
}
if (blockno == 1) {
if (data_read[0] != data_read_orig[0]) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]);
isok = PM3_SUCCESS;
goto_out = true;
}
if (data_read[7] != data_read_orig[7]) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]);
const char *flag_names[8] = {
"RA",
"Fprod0",
"Fprod1",
"Crypt0 (*1)",
"Crypt1 (*0)",
"Coding0",
"Coding1",
"Fpers (*1)"
};
PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed");
PrintAndLogEx(INFO, "---------------------------------------");
for (int i = 7; i >= 0; --i) {
int bit1 = (data_read_orig[7] >> i) & 1;
int bit2 = (data_read[7] >> i) & 1;
PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2);
}
isok = PM3_SUCCESS;
goto_out = true;
}
// if more OTP bits set..
if (data_read[1] > data_read_orig[1] ||
data_read[2] > data_read_orig[2]) {
PrintAndLogEx(SUCCESS, "More OTP bits got set!!!");
data_read[7] = 0xBC;
res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
if (res != PM3_SUCCESS) {
PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )");
}
isok = PM3_SUCCESS;
goto_out = true;
}
}
if (goto_out) {
goto out;
}
}
}
if (tear_success) { // tearoff succeeded with expected values
if (tear_success) { // tearoff succeeded with expected values
read_ok = true;
tear_success = true;
read_ok = true;
tear_success = true;
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
, sprint_hex_inrow(data_read, sizeof(data_read)),
(expected_values) ? _GREEN_(" -> Expected values!") : ""
);
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
, sprint_hex_inrow(data_read, sizeof(data_read)),
(expected_values) ? _GREEN_(" -> Expected values!") : ""
);
}
loop_count++;
loop_count++;
if (loop_count == tearoff_loop) {
tearoff_start += tearoff_increment;
loop_count = 0;
}
if (loop_count == tearoff_loop) {
tearoff_start += tearoff_increment;
loop_count = 0;
}
if (tearoff_sleep) {
msleep(tearoff_sleep);
if (tearoff_sleep) {
msleep(tearoff_sleep);
}
}
}
@ -3788,9 +3914,9 @@ static void HFiClassCalcNewKey(uint8_t *CSN, uint8_t *OLDKEY, uint8_t *NEWKEY, u
xor_div_key[i] = old_div_key[i] ^ new_div_key[i];
}
if (verbose) {
PrintAndLogEx(SUCCESS, "Old div key......... %s", sprint_hex(old_div_key, 8));
PrintAndLogEx(SUCCESS, "New div key......... %s", sprint_hex(new_div_key, 8));
PrintAndLogEx(SUCCESS, "Xor div key......... " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8));
PrintAndLogEx(SUCCESS, "Old div key........ %s", sprint_hex(old_div_key, 8));
PrintAndLogEx(SUCCESS, "New div key........ %s", sprint_hex(new_div_key, 8));
PrintAndLogEx(SUCCESS, "Xor div key........ " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8));
}
}
@ -4414,9 +4540,15 @@ static int iclass_recover(uint8_t key[8], uint32_t index_start, uint32_t loop, u
WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp);
if (resp.status == PM3_SUCCESS) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!"));
repeat = false;
} else if (resp.status == PM3_EOPABORTED) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _YELLOW_("user aborted"));
repeat = false;
} else if (resp.status == PM3_ESOFT) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _RED_("failed/errors"));
repeat = false;
} else if (resp.status == PM3_EINVARG) {
@ -4455,7 +4587,7 @@ void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uin
}
}
static int CmdHFiClassLegRecLookUp(const char *Cmd) {
static int CmdHFiClassLegBrute(const char *Cmd) {
//Standalone Command Start
CLIParserContext *ctx;
@ -4682,7 +4814,7 @@ static int CmdHFiClassLegacyRecSim(void) {
PrintAndLogEx(SUCCESS, "Original Key: " _GREEN_("%s"), sprint_hex(original_key, sizeof(original_key)));
PrintAndLogEx(SUCCESS, "Weak Key: " _GREEN_("%s"), sprint_hex(key, sizeof(key)));
PrintAndLogEx(SUCCESS, "Key Updates Required to Weak Key: " _GREEN_("%d"), index);
PrintAndLogEx(SUCCESS, "Estimated Time: ~" _GREEN_("%d")" hours", index / 6545);
PrintAndLogEx(SUCCESS, "Estimated Time: ~" _GREEN_("%d")" hours", index / 7250);
}
index++;
@ -4761,8 +4893,14 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) {
return PM3_EINVARG;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "---------------------------------------");
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
iclass_recover(macs, index, loop, no_first_auth, debug, test, allnight);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, _YELLOW_("If the process completed successfully, you can now run 'hf iclass legbrute' with the partial key found."));
PrintAndLogEx(NORMAL, "");
@ -5773,7 +5911,7 @@ static command_t CommandTable[] = {
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
{"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"},
{"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Recovers 24 bits of the diversified key of a legacy card provided a valid nr-mac combination"},
{"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial diversified key, provided 24 bits of the key and two valid nr-macs"},
{"legbrute", CmdHFiClassLegBrute, AlwaysAvailable, "Bruteforces 40 bits of a partial diversified key, provided 24 bits of the key and two valid nr-macs"},
{"unhash", CmdHFiClassUnhash, AlwaysAvailable, "Reverses a diversified key to retrieve hash0 pre-images after DES encryption"},
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
@ -5927,33 +6065,46 @@ int info_iclass(bool shallow_mod) {
uint8_t cardtype = get_mem_config(hdr);
PrintAndLogEx(SUCCESS, " Card type.... " _GREEN_("%s"), card_types[cardtype]);
if (HF14B_picopass_reader(false, false)) {
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)"));
} else {
if (memcmp(hdr->csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("NEW Silicon (No 14b support)"));
} else {
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)"));
}
if (legacy) {
int res = PM3_ESOFT;
uint8_t key_type = 0x88; // debit key
uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0};
// we take all raw bytes from response
memcpy(dump, p_response, sizeof(picopass_hdr_t));
bool found_aa1 = false;
bool found_aa2 = false;
uint8_t key[8] = {0};
for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) {
memcpy(key, iClass_Key_Table[i], sizeof(key));
res = iclass_read_block_ex(key, 6, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 6), false);
if (found_aa1 == false) {
res = iclass_read_block_ex(key, 6, ICLASS_DEBIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 6), false);
if (res == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
found_aa1 = true;
}
}
res = iclass_read_block_ex(key, 6, ICLASS_CREDIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false);
if (res == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
PrintAndLogEx(SUCCESS, " AA2 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
found_aa2 = true;
}
if (found_aa1 && found_aa2) {
break;
}
}
if (res == PM3_SUCCESS) {
res = iclass_read_block_ex(key, 7, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false);
if (found_aa1) {
res = iclass_read_block_ex(key, 7, ICLASS_DEBIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false);
if (res == PM3_SUCCESS) {
BLOCK79ENCRYPTION aa1_encryption = (dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);

View file

@ -48,6 +48,301 @@
#include "mifare/mifarehost.h"
#include "crypto/originality.h"
// Defines for Saflok parsing
#define SAFLOK_YEAR_OFFSET 1980
#define SAFLOK_BASIC_ACCESS_BYTE_NUM 17
#define SAFLOK_KEY_LENGTH 6
#define SAFLOK_UID_LENGTH 4 // Matches Mifare 4-byte UID
#define SAFLOK_MAGIC_TABLE_SIZE 192
#define SAFLOK_CHECK_SECTOR 1
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
// Structure for Saflok key levels
typedef struct {
uint8_t level_num;
const char *level_name;
} SaflokKeyLevel;
// Static array for Saflok key levels
static const SaflokKeyLevel saflok_key_levels[] = {
{1, "Guest Key"},
{2, "Connectors"},
{3, "Suite"},
{4, "Limited Use"},
{5, "Failsafe"},
{6, "Inhibit"},
{7, "Pool/Meeting Master"},
{8, "Housekeeping"},
{9, "Floor Key"},
{10, "Section Key"},
{11, "Rooms Master"},
{12, "Grand Master"},
{13, "Emergency"},
{14, "Electronic Lockout"},
{15, "Secondary Programming Key (SPK)"},
{16, "Primary Programming Key (PPK)"},
};
// Lookup table for Saflok decryption
static const uint8_t saflok_c_aDecode[256] = {
0xEA, 0x0D, 0xD9, 0x74, 0x4E, 0x28, 0xFD, 0xBA, 0x7B, 0x98, 0x87, 0x78, 0xDD, 0x8D, 0xB5,
0x1A, 0x0E, 0x30, 0xF3, 0x2F, 0x6A, 0x3B, 0xAC, 0x09, 0xB9, 0x20, 0x6E, 0x5B, 0x2B, 0xB6,
0x21, 0xAA, 0x17, 0x44, 0x5A, 0x54, 0x57, 0xBE, 0x0A, 0x52, 0x67, 0xC9, 0x50, 0x35, 0xF5,
0x41, 0xA0, 0x94, 0x60, 0xFE, 0x24, 0xA2, 0x36, 0xEF, 0x1E, 0x6B, 0xF7, 0x9C, 0x69, 0xDA,
0x9B, 0x6F, 0xAD, 0xD8, 0xFB, 0x97, 0x62, 0x5F, 0x1F, 0x38, 0xC2, 0xD7, 0x71, 0x31, 0xF0,
0x13, 0xEE, 0x0F, 0xA3, 0xA7, 0x1C, 0xD5, 0x11, 0x4C, 0x45, 0x2C, 0x04, 0xDB, 0xA6, 0x2E,
0xF8, 0x64, 0x9A, 0xB8, 0x53, 0x66, 0xDC, 0x7A, 0x5D, 0x03, 0x07, 0x80, 0x37, 0xFF, 0xFC,
0x06, 0xBC, 0x26, 0xC0, 0x95, 0x4A, 0xF1, 0x51, 0x2D, 0x22, 0x18, 0x01, 0x79, 0x5E, 0x76,
0x1D, 0x7F, 0x14, 0xE3, 0x9E, 0x8A, 0xBB, 0x34, 0xBF, 0xF4, 0xAB, 0x48, 0x63, 0x55, 0x3E,
0x56, 0x8C, 0xD1, 0x12, 0xED, 0xC3, 0x49, 0x8E, 0x92, 0x9D, 0xCA, 0xB1, 0xE5, 0xCE, 0x4D,
0x3F, 0xFA, 0x73, 0x05, 0xE0, 0x4B, 0x93, 0xB2, 0xCB, 0x08, 0xE1, 0x96, 0x19, 0x3D, 0x83,
0x39, 0x75, 0xEC, 0xD6, 0x3C, 0xD0, 0x70, 0x81, 0x16, 0x29, 0x15, 0x6C, 0xC7, 0xE7, 0xE2,
0xF6, 0xB7, 0xE8, 0x25, 0x6D, 0x3A, 0xE6, 0xC8, 0x99, 0x46, 0xB0, 0x85, 0x02, 0x61, 0x1B,
0x8B, 0xB3, 0x9F, 0x0B, 0x2A, 0xA8, 0x77, 0x10, 0xC1, 0x88, 0xCC, 0xA4, 0xDE, 0x43, 0x58,
0x23, 0xB4, 0xA1, 0xA5, 0x5C, 0xAE, 0xA9, 0x7E, 0x42, 0x40, 0x90, 0xD2, 0xE9, 0x84, 0xCF,
0xE4, 0xEB, 0x47, 0x4F, 0x82, 0xD4, 0xC5, 0x8F, 0xCD, 0xD3, 0x86, 0x00, 0x59, 0xDF, 0xF2,
0x0C, 0x7C, 0xC6, 0xBD, 0xF9, 0x7D, 0xC4, 0x91, 0x27, 0x89, 0x32, 0x72, 0x33, 0x65, 0x68,
0xAF
};
// Function to decrypt Saflok card data
static void DecryptSaflokCardData(
const uint8_t strCard[SAFLOK_BASIC_ACCESS_BYTE_NUM],
// int length, // length is always SAFLOK_BASIC_ACCESS_BYTE_NUM
uint8_t decryptedCard[SAFLOK_BASIC_ACCESS_BYTE_NUM]) {
int i;
int num;
int num2;
int num3;
int num4;
int b = 0;
int b2 = 0;
for (i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM; i++) {
num = saflok_c_aDecode[strCard[i]] - (i + 1);
if (num < 0) num += 256;
decryptedCard[i] = num;
}
b = decryptedCard[10];
b2 = b & 1;
for (num2 = SAFLOK_BASIC_ACCESS_BYTE_NUM; num2 > 0; num2--) {
b = decryptedCard[num2 - 1];
for (num3 = 8; num3 > 0; num3--) {
num4 = num2 + num3;
if (num4 > SAFLOK_BASIC_ACCESS_BYTE_NUM) num4 -= SAFLOK_BASIC_ACCESS_BYTE_NUM;
int b3 = decryptedCard[num4 - 1];
int b4 = (b3 & 0x80) >> 7;
b3 = ((b3 << 1) & 0xFF) | b2;
b2 = (b & 0x80) >> 7;
b = ((b << 1) & 0xFF) | b4;
decryptedCard[num4 - 1] = b3;
}
decryptedCard[num2 - 1] = b;
}
}
// Function to calculate Saflok checksum
static uint8_t CalculateCheckSum(uint8_t data[SAFLOK_BASIC_ACCESS_BYTE_NUM]) {
int sum = 0;
for (int i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM - 1; i++) {
sum += data[i];
}
sum = 255 - (sum & 0xFF);
return sum & 0xFF;
}
// Function to parse and print Saflok data
static void ParseAndPrintSaflokData(const sector_t *sector0_info, const sector_t *sector1_info) {
(void)sector1_info; // Not directly used for payload parsing currently
if (!sector0_info) {
PrintAndLogEx(WARNING, "Saflok: Sector 0 information not available for parsing.");
return;
}
uint8_t key_bytes_for_s0[MIFARE_KEY_SIZE];
uint8_t key_type_for_s0; // CORRECTED: Was MifareKeyType, now uint8_t
bool s0_key_found = false;
// Prioritize Key A for Sector 0 if available
if (sector0_info->foundKey[MF_KEY_A]) {
num_to_bytes(sector0_info->Key[MF_KEY_A], MIFARE_KEY_SIZE, key_bytes_for_s0);
key_type_for_s0 = MF_KEY_A; // MF_KEY_A is typically #define'd as 0x60
s0_key_found = true;
PrintAndLogEx(DEBUG, "Saflok: Using Sector 0 Key A for reading blocks.");
} else if (sector0_info->foundKey[MF_KEY_B]) { // Fallback to Key B for Sector 0
num_to_bytes(sector0_info->Key[MF_KEY_B], MIFARE_KEY_SIZE, key_bytes_for_s0);
key_type_for_s0 = MF_KEY_B; // MF_KEY_B is typically #define'd as 0x61
s0_key_found = true;
PrintAndLogEx(DEBUG, "Saflok: Using Sector 0 Key B for reading blocks.");
}
if (!s0_key_found) {
PrintAndLogEx(WARNING, "Saflok: No known keys for Sector 0. Cannot read blocks 1 & 2 for parsing.");
return;
}
uint8_t block1_content[MFBLOCK_SIZE];
uint8_t block2_content[MFBLOCK_SIZE];
// Read absolute block 1 (data block within sector 0)
if (mf_read_block(1, key_type_for_s0, key_bytes_for_s0, block1_content) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Saflok: Failed to read card Block 1 using Sector 0 %s key.", (key_type_for_s0 == MF_KEY_A) ? "A" : "B");
return;
}
PrintAndLogEx(DEBUG, "Saflok: Successfully read card Block 1.");
// Read absolute block 2 (data block within sector 0)
if (mf_read_block(2, key_type_for_s0, key_bytes_for_s0, block2_content) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Saflok: Failed to read card Block 2 using Sector 0 %s key.", (key_type_for_s0 == MF_KEY_A) ? "A" : "B");
return;
}
PrintAndLogEx(DEBUG, "Saflok: Successfully read card Block 2.");
uint8_t basicAccess[SAFLOK_BASIC_ACCESS_BYTE_NUM];
uint8_t decodedBA[SAFLOK_BASIC_ACCESS_BYTE_NUM];
memcpy(basicAccess, block1_content, 16); // 16 bytes from Block 1
memcpy(basicAccess + 16, block2_content, 1); // 1 byte from Block 2
DecryptSaflokCardData(basicAccess, decodedBA);
// Byte 0: Key level, LED warning bit, and subgroup functions
uint8_t key_level = (decodedBA[0] & 0xF0) >> 4;
uint8_t led_warning = (decodedBA[0] & 0x08) >> 3;
// Byte 1: Key ID
uint8_t key_id = decodedBA[1];
// Byte 2 & 3: KeyRecord, including OpeningKey flag
uint8_t key_record_high = decodedBA[2] & 0x7F;
uint8_t opening_key = (decodedBA[2] & 0x80) >> 7;
uint16_t key_record = (key_record_high << 8) | decodedBA[3];
// Byte 5 & 6: EncryptSequence + Combination
uint16_t sequence_combination_number = ((decodedBA[5] & 0x0F) << 8) | decodedBA[6];
// Byte 7: OverrideDeadbolt and Days
uint8_t override_deadbolt = (decodedBA[7] & 0x80) >> 7;
uint8_t restricted_weekday = decodedBA[7] & 0x7F;
// Weekday names array
static const char *weekdays[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
// Buffer to store the resulting string (sufficient size for all weekdays)
char restricted_weekday_string[128] = {0};
int restricted_count = 0;
// Check each bit from Monday to Sunday
for (int i = 0; i < 7; i++) {
if (restricted_weekday & (0b01000000 >> i)) {
// If the bit is set, append the corresponding weekday to the buffer
if (restricted_count > 0) {
strcat(restricted_weekday_string, ", ");
}
strcat(restricted_weekday_string, weekdays[i]);
restricted_count++;
}
}
// Determine if all weekdays are restricted
if (restricted_weekday == 0b01111100) {
strcpy(restricted_weekday_string, "weekdays");
}
// If there are specific restricted days
else if (restricted_weekday == 0b00000011) {
strcpy(restricted_weekday_string, "weekends");
}
// If no weekdays are restricted
else if (restricted_weekday == 0) {
strcpy(restricted_weekday_string, "none");
}
// Bytes 14-15: Property number and part of creation year
uint8_t creation_year_high_bits = (decodedBA[14] & 0xF0);
uint16_t property_id = ((decodedBA[14] & 0x0F) << 8) | decodedBA[15];
// Bytes 11-13: Creation date since SAFLOK_YEAR_OFFSET Jan 1st
uint16_t creation_year = (((decodedBA[11] & 0xF0) >> 4) + SAFLOK_YEAR_OFFSET) | creation_year_high_bits;
uint8_t creation_month = decodedBA[11] & 0x0F;
uint8_t creation_day = (decodedBA[12] >> 3) & 0x1F;
uint8_t creation_hour = ((decodedBA[12] & 0x07) << 2) | ((decodedBA[13] & 0xC0) >> 6);
uint8_t creation_minute = decodedBA[13] & 0x3F;
// Bytes 8-10: Expiry interval / absolute time components
uint8_t interval_year_val = (decodedBA[8] >> 4);
uint8_t interval_month_val = decodedBA[8] & 0x0F;
uint8_t interval_day_val = (decodedBA[9] >> 3) & 0x1F;
uint8_t expiry_hour = ((decodedBA[9] & 0x07) << 2) | ((decodedBA[10] & 0xC0) >> 6);
uint8_t expiry_minute = decodedBA[10] & 0x3F;
uint16_t expire_year = creation_year + interval_year_val;
uint8_t expire_month = creation_month + interval_month_val;
uint8_t expire_day = creation_day + interval_day_val;
// Handle month rollover for expiration
while (expire_month > 12) {
expire_month -= 12;
expire_year++;
}
// Handle day rollover for expiration
static const uint8_t days_in_month_lookup[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 1-indexed month
if (expire_month > 0 && expire_month <= 12) {
while (true) {
uint8_t max_days = days_in_month_lookup[expire_month];
if (expire_month == 2 && (expire_year % 4 == 0 && (expire_year % 100 != 0 || expire_year % 400 == 0))) {
max_days = 29; // Leap year
}
if (expire_day <= max_days) {
break;
}
if (max_days == 0) { // Should not happen with valid month
PrintAndLogEx(WARNING, "Saflok: Invalid day/month for expiration rollover calculation.");
break;
}
expire_day -= max_days;
expire_month++;
if (expire_month > 12) {
expire_month = 1;
expire_year++;
}
}
} else if (expire_month != 0) { // Allow 0 if it signifies no expiration or error
PrintAndLogEx(WARNING, "Saflok: Invalid expiration month (%u) before day rollover.", expire_month);
}
uint8_t checksum = decodedBA[16];
uint8_t checksum_calculated = CalculateCheckSum(decodedBA);
bool checksum_valid = (checksum_calculated == checksum);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Saflok Details"));
PrintAndLogEx(SUCCESS, "Key Level: %u (%s)", saflok_key_levels[key_level].level_num, saflok_key_levels[key_level].level_name);
PrintAndLogEx(SUCCESS, "LED Warning: %s", led_warning ? "Yes" : "No");
PrintAndLogEx(SUCCESS, "Key ID: %u (0x%02X)", key_id, key_id);
PrintAndLogEx(SUCCESS, "Key Record: %u (0x%04X)", key_record, key_record);
PrintAndLogEx(SUCCESS, "Opening Key: %s", opening_key ? "Yes" : "No");
PrintAndLogEx(SUCCESS, "Sequence Number & Combination: %u (0x%02X)", sequence_combination_number, sequence_combination_number);
PrintAndLogEx(SUCCESS, "Override Deadbolt: %s", override_deadbolt ? "Yes" : "No");
PrintAndLogEx(SUCCESS, "Restricted Weekdays: %s", restricted_weekday_string);
PrintAndLogEx(SUCCESS, "Property ID: %u (0x%04X)", property_id, property_id);
PrintAndLogEx(SUCCESS, "Creation Date: %04u-%02u-%02u %02u:%02u", creation_year, creation_month, creation_day, creation_hour, creation_minute);
PrintAndLogEx(SUCCESS, "Expiration Date: %04u-%02u-%02u %02u:%02u", expire_year, expire_month, expire_day, expiry_hour, expiry_minute);
PrintAndLogEx(SUCCESS, "Checksum Valid: %s", checksum_valid ? "Yes" : "No");
}
static int CmdHelp(const char *Cmd);
/*
@ -9872,7 +10167,8 @@ static int CmdHF14AMfInfo(const char *Cmd) {
&& card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x45", 4) == 0) {
PrintAndLogEx(SUCCESS, "NXP MF1ICS5004");
} else if (fKeyType == MF_KEY_BD) {
PrintAndLogEx(SUCCESS, _RED_("Unknown card with backdoor, please report details!"));
PrintAndLogEx(SUCCESS, _RED_("Unknown card with backdoor"));
PrintAndLogEx(INFO, "Please report details!");
} else
// other cards
if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x46", 4) == 0) {
@ -9886,11 +10182,12 @@ static int CmdHF14AMfInfo(const char *Cmd) {
} else if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\xc0", 4) == 0) {
PrintAndLogEx(SUCCESS, "NXP MF1ICS5035");
} else {
PrintAndLogEx(SUCCESS, "unknown");
PrintAndLogEx(SUCCESS, "n/a");
}
if (e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) {
if (keycnt > 1 && e_sector != NULL && e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) {
PrintAndLogEx(SUCCESS, "dormakaba Saflok detected");
ParseAndPrintSaflokData(&e_sector[0], &e_sector[1]);
}
} else {
@ -10229,6 +10526,64 @@ static int CmdHF14AMfISEN(const char *Cmd) {
return PM3_SUCCESS;
}
static int CmdHF14AMfBambuKeys(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf bambukeys",
"Generate keys for a Bambu Lab filament tag",
"hf mf bambukeys -r\n"
"hf mf bambukeys -r -d\n"
"hf mf bambukeys -u 11223344\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0("u", "uid", "<hex>", "UID (4 hex bytes)"),
arg_lit0("r", NULL, "Read UID from tag"),
arg_lit0("d", NULL, "Dump keys to file"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int u_len = 0;
uint8_t uid[7] = {0x00};
CLIGetHexWithReturn(ctx, 1, uid, &u_len);
bool use_tag = arg_get_lit(ctx, 2);
bool dump_keys = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
if (use_tag) {
// read uid from tag
int res = mf_read_uid(uid, &u_len, NULL);
if (res != PM3_SUCCESS) {
return res;
}
}
if (u_len != 4) {
PrintAndLogEx(WARNING, "Key must be 4 hex bytes");
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "-----------------------------------");
PrintAndLogEx(INFO, " UID 4b... " _YELLOW_("%s"), sprint_hex(uid, 4));
PrintAndLogEx(INFO, "-----------------------------------");
uint8_t keys[32 * 6];
mfc_algo_bambu_all(uid, (void *)keys);
for (int block = 0; block < 32; block++) {
PrintAndLogEx(INFO, "%d: %012" PRIX64, block, bytes_to_num(keys + (block * 6), 6));
}
if (dump_keys) {
char fn[FILE_PATH_SIZE] = {0};
snprintf(fn, sizeof(fn), "hf-mf-%s-key", sprint_hex_inrow(uid, 4));
saveFileEx(fn, ".bin", keys, 32 * 6, spDump);
}
return PM3_SUCCESS;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"list", CmdHF14AMfList, AlwaysAvailable, "List MIFARE history"},
@ -10247,6 +10602,7 @@ static command_t CommandTable[] = {
{"fchk", CmdHF14AMfChk_fast, IfPm3Iso14443a, "Check keys fast, targets all keys on card"},
{"decrypt", CmdHf14AMfDecryptBytes, AlwaysAvailable, "Decrypt Crypto1 data from sniff or trace"},
{"supercard", CmdHf14AMfSuperCard, IfPm3Iso14443a, "Extract info from a `super card`"},
{"bambukeys", CmdHF14AMfBambuKeys, AlwaysAvailable, "Generate key table for Bambu Lab filament tag"},
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"},
{"auth4", CmdHF14AMfAuth4, IfPm3Iso14443a, "ISO14443-4 AES authentication"},
{"acl", CmdHF14AMfAcl, AlwaysAvailable, "Decode and print MIFARE Classic access rights bytes"},

View file

@ -299,7 +299,7 @@ static const char *getUlev1CardSizeStr(uint8_t fsize) {
// is LSB set?
if (fsize & 1)
snprintf(buf, sizeof(buf), "%02X, (%u <-> %u bytes)", fsize, usize, lsize);
snprintf(buf, sizeof(buf), "%02X, (%u - %u bytes)", fsize, usize, lsize);
else
snprintf(buf, sizeof(buf), "%02X, (%u bytes)", fsize, lsize);
return buf;
@ -3925,17 +3925,17 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mfu aesauth",
"Tests AES key on Mifare Ultralight AES tags.\n"
"If key is not specified, null key will be tried.\n"
"If no key is specified, null key will be tried.\n"
"Key index 0: DataProtKey (default)\n"
"Key index 1: UIDRetrKey\n"
"Key index 2: OriginalityKey\n",
"hf mfu aesauth\n"
"hf mfu aesauth --key <32 hex chars> --index <0..2>"
"hf mfu aesauth --key <16 hex bytes> --index <0..2>"
);
void *argtable[] = {
arg_param_begin,
arg_str0(NULL, "key", "<hex>", "Authentication key (16 bytes in hex)"),
arg_str0(NULL, "key", "<hex>", "AES key (16 hex bytes)"),
arg_int0("i", "index", "<0..2>", "Key index, default: 0"),
arg_lit0("k", NULL, "Keep field on (only if a key is provided)"),
arg_param_end

View file

@ -356,6 +356,7 @@ const static vocabulary_t vocabulary[] = {
{ 0, "hf mf fchk" },
{ 1, "hf mf decrypt" },
{ 0, "hf mf supercard" },
{ 1, "hf mf bambukeys" },
{ 0, "hf mf auth4" },
{ 1, "hf mf acl" },
{ 0, "hf mf dump" },

View file

@ -1409,6 +1409,20 @@ void str_inverse_bin(char *buf, size_t len) {
}
}
void str_trim(char *s) {
if (s == NULL) {
return;
}
// handle empty string
if (!*s) {
return;
}
char *ptr;
for (ptr = s + strlen(s) - 1; (ptr >= s) && isspace(*ptr); --ptr);
ptr[1] = '\0';
}
/**
* Converts a hex string to component "hi2", "hi" and "lo" 32-bit integers

View file

@ -168,10 +168,12 @@ void str_creplace(char *buf, size_t len, char from, char to);
void str_reverse(char *buf, size_t len);
void str_inverse_hex(char *buf, size_t len);
void str_inverse_bin(char *buf, size_t len);
void str_trim(char *s);
char *str_dup(const char *src);
char *str_ndup(const char *src, size_t len);
size_t str_nlen(const char *src, size_t maxlen);
int hexstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str);
int binstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str);
int binarray_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const uint8_t *arr, int arrlen);

View file

@ -3718,7 +3718,7 @@
},
"hf iclass tear": {
"command": "hf iclass tear",
"description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards",
"description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`",
"notes": [
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600",
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600",
@ -3742,9 +3742,10 @@
"-i <dec> tearoff delay increment (in us) - default 10",
"-e <dec> tearoff delay end (in us) must be a higher value than the start delay",
"--loop <dec> number of times to loop per tearoff time",
"--sleep <ms> Sleep between each tear"
"--sleep <ms> Sleep between each tear",
"--arm Runs the commands on device side and tries to stabilize tears"
],
"usage": "hf iclass tear [-hv] [-k <hex>] [--ki <dec>] --blk <dec> -d <hex> [-m <hex>] [--credit] [--elite] [--raw] [--nr] [--shallow] -s <dec> [-i <dec>] [-e <dec>] [--loop <dec>] [--sleep <ms>]"
"usage": "hf iclass tear [-hv] [-k <hex>] [--ki <dec>] --blk <dec> -d <hex> [-m <hex>] [--credit] [--elite] [--raw] [--nr] [--shallow] -s <dec> [-i <dec>] [-e <dec>] [--loop <dec>] [--sleep <ms>] [--arm]"
},
"hf iclass unhash": {
"command": "hf iclass unhash",
@ -4411,6 +4412,23 @@
],
"usage": "hf mf autopwn [-hablv] [-k <hex>]... [-s <dec>] [-f <fn>] [--suffix <txt>] [--slow] [--mem] [--ns] [--mini] [--1k] [--2k] [--4k] [--in] [--im] [--is] [--ia] [--i2] [--i5]"
},
"hf mf bambukeys": {
"command": "hf mf bambukeys",
"description": "Generate keys for a Bambu Lab filament tag",
"notes": [
"hf mf bambukeys -r",
"hf mf bambukeys -r -d",
"hf mf bambukeys -u 11223344"
],
"offline": true,
"options": [
"-h, --help This help",
"-u, --uid <hex> UID (4 hex bytes)",
"-r Read UID from tag",
"-d Dump keys to file"
],
"usage": "hf mf bambukeys [-hrd] [-u <hex>]"
},
"hf mf brute": {
"command": "hf mf brute",
"description": "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.",
@ -5145,7 +5163,7 @@
},
"hf mf help": {
"command": "hf mf help",
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace bambukeys Generate key table for Bambu Lab filament tag acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
"notes": [
"hf mf list --frame -> show frame delay times",
"hf mf list -1 -> use trace buffer"
@ -7061,15 +7079,15 @@
},
"hf mfu aesauth": {
"command": "hf mfu aesauth",
"description": "Tests AES key on Mifare Ultralight AES tags. If key is not specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey",
"description": "Tests AES key on Mifare Ultralight AES tags. If no key is specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey",
"notes": [
"hf mfu aesauth",
"hf mfu aesauth --key <32 hex chars> --index <0..2>"
"hf mfu aesauth --key <16 hex bytes> --index <0..2>"
],
"offline": false,
"options": [
"-h, --help This help",
"--key <hex> Authentication key (16 bytes in hex)",
"--key <hex> AES key (16 hex bytes)",
"-i, --index <0..2> Key index, default: 0",
"-k Keep field on (only if a key is provided)"
],
@ -13352,8 +13370,8 @@
}
},
"metadata": {
"commands_extracted": 767,
"commands_extracted": 768,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2025-05-25T12:38:36"
"extracted_on": "2025-05-29T23:30:20"
}
}

View file

@ -527,6 +527,7 @@ Check column "offline" for their availability.
|`hf mf fchk `|N |`Check keys fast, targets all keys on card`
|`hf mf decrypt `|Y |`Decrypt Crypto1 data from sniff or trace`
|`hf mf supercard `|N |`Extract info from a `super card``
|`hf mf bambukeys `|Y |`Generate key table for Bambu Lab filament tag`
|`hf mf auth4 `|N |`ISO14443-4 AES authentication`
|`hf mf acl `|Y |`Decode and print MIFARE Classic access rights bytes`
|`hf mf dump `|N |`Dump MIFARE Classic tag to binary file`

View file

@ -87,6 +87,17 @@ typedef struct {
uint8_t mac[4];
} PACKED iclass_writeblock_req_t;
// iCLASS tearoff block request data structure
typedef struct {
iclass_auth_req_t req;
uint8_t data[8];
uint8_t mac[4];
int tear_start;
int tear_end;
int increment;
int tear_loop;
} PACKED iclass_tearblock_req_t;
// iCLASS write block request data structure
typedef struct {
iclass_auth_req_t req;

View file

@ -668,6 +668,7 @@ typedef struct {
#define CMD_HF_ICLASS_RESTORE 0x039B
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
#define CMD_HF_ICLASS_RECOVER 0x039D
#define CMD_HF_ICLASS_TEARBL 0x039E
// For ISO1092 / FeliCa

View file

@ -272,7 +272,9 @@ static uint64_t **unpredictable_nested(NtpKs1List *pNKL, uint32_t keyCounts[]) {
pthread_cond_wait(&status_cond, &status_mutex);
activeThreads = 0;
for (int i = 0; i < NUM_THREADS; i++) {
if (thread_status[i]) activeThreads++;
if (thread_status[i]) {
activeThreads++;
}
}
}

View file

@ -34,8 +34,8 @@ endif
# OS X needs linking to openssl
ifeq ($(USE_BREW),1)
MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@1.1/include
MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@1.1/lib
MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@3.5/include
MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@3.5/lib
endif
ifeq ($(USE_MACPORTS),1)