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

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