mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-21 05:43:48 -07:00
Merge branch 'RfidResearchGroup:master' into master
This commit is contained in:
commit
34ddd4a75c
26 changed files with 1425 additions and 275 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(¶ms, 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(¶ms, 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);
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue