mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-20 21:33:47 -07:00
Merge branch 'RfidResearchGroup:master' into master
This commit is contained in:
commit
34ddd4a75c
26 changed files with 1425 additions and 275 deletions
|
@ -3,17 +3,22 @@ All notable changes to this project will be documented in this file.
|
||||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||||
|
|
||||||
## [unreleased][unreleased]
|
## [unreleased][unreleased]
|
||||||
|
- Changed `hf iclass tear` - readability improvements for erase phase (@antiklesys)
|
||||||
|
- Changed `hf iclass legrec` - code optimizations gaining a ~8% speed increase (@antiklesys)
|
||||||
|
- Modified `hf iclass tear` - now has a device side implementation also. (@antiklesys) (@iceman1001)
|
||||||
|
- Changed `hf iclass info` - now uses CSN values based checks (@antiklesys)
|
||||||
- Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001)
|
- Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001)
|
||||||
- Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001)
|
- Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001)
|
||||||
- Changed `hw tearoff` - the device side message is now debug log controlled (@iceman1001)
|
- Changed `hw tearoff` - the device side message is now debug log controlled (@iceman1001)
|
||||||
- Changed `pm3.sh` - Serial ports enumeration on Proxspace3.xx / MINGW environments, now using powershell.exe since wmic is deprecated (@iceman1001)
|
- Changed `pm3.sh` - Serial ports enumeration on Proxspace3.xx / MINGW environments, now using powershell.exe since wmic is deprecated (@iceman1001)
|
||||||
- Fixed and updated `hf iclass trbl` to correctly use the credit key when passed and show partial tearoff results (@antiklesys)
|
- Fixed and updated `hf iclass trbl` to correctly use the credit key when passed and show partial tearoff results (@antiklesys)
|
||||||
- Fixed `hf iclass legbrute` was not correctly parsin the index value
|
- Fixed `hf iclass legbrute` was not correctly parsing the index value
|
||||||
- Fixed `hf mf ekeyprn` - failed to download emulator memory due to wrong size calculation (@iceman1001)
|
- Fixed `hf mf ekeyprn` - failed to download emulator memory due to wrong size calculation (@iceman1001)
|
||||||
- Fixed `hf mf fchk --mem` to actually use flash dict (@doegox)
|
- Fixed `hf mf fchk --mem` to actually use flash dict (@doegox)
|
||||||
- Fixed `make install` on OSX thanks DaveItsLong (@doegox)
|
- Fixed `make install` on OSX thanks DaveItsLong (@doegox)
|
||||||
- Added new standalone mode `HF_ST25_TEAROFF` to store/restore ST25TB tags with tearoff for counters (@seclabz)
|
- Added new standalone mode `HF_ST25_TEAROFF` to store/restore ST25TB tags with tearoff for counters (@seclabz)
|
||||||
- Added `hf_mfu_ultra.lua` script enables restoring dump to ULTRA/UL-5 tags and clearing previously written ULTRA tags (@mak-42)
|
- Added `hf_mfu_ultra.lua` script enables restoring dump to ULTRA/UL-5 tags and clearing previously written ULTRA tags (@mak-42)
|
||||||
|
- Fixed `hf mfu sim` to make persistent the counter increases in the emulator memory (@sup3rgiu)
|
||||||
|
|
||||||
## [Blue Ice.4.20142][2025-03-25]
|
## [Blue Ice.4.20142][2025-03-25]
|
||||||
- Added `des_talk.py` script for easier MIFARE DESFire handling (@trigat)
|
- Added `des_talk.py` script for easier MIFARE DESFire handling (@trigat)
|
||||||
|
|
|
@ -233,10 +233,8 @@ static void become_card(void) {
|
||||||
|
|
||||||
tag_response_info_t *canned;
|
tag_response_info_t *canned;
|
||||||
uint32_t cuid;
|
uint32_t cuid;
|
||||||
uint32_t counters[3] = { 0 };
|
|
||||||
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
|
|
||||||
uint8_t pages;
|
uint8_t pages;
|
||||||
SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, counters, tearings, &pages);
|
SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, &pages);
|
||||||
|
|
||||||
DbpString(_CYAN_("[@]") " Setup done - entering emulation loop");
|
DbpString(_CYAN_("[@]") " Setup done - entering emulation loop");
|
||||||
int fromReaderLen;
|
int fromReaderLen;
|
||||||
|
|
|
@ -379,7 +379,7 @@ void RunMod(void) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
|
|
||||||
// tag type: 11 = ISO/IEC 14443-4 - javacard (JCOP)
|
// tag type: 11 = ISO/IEC 14443-4 - javacard (JCOP)
|
||||||
if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) {
|
if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
DbpString(_RED_("Error initializing the emulation process!"));
|
DbpString(_RED_("Error initializing the emulation process!"));
|
||||||
|
|
|
@ -268,7 +268,7 @@ void RunMod() {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
|
|
||||||
// 4 = ISO/IEC 14443-4 - javacard (JCOP)
|
// 4 = ISO/IEC 14443-4 - javacard (JCOP)
|
||||||
if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) {
|
if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
DbpString(_RED_("Error initializing the emulation process!"));
|
DbpString(_RED_("Error initializing the emulation process!"));
|
||||||
|
|
|
@ -118,8 +118,6 @@ void RunMod(void) {
|
||||||
uint8_t tagType = 10; // 10 = ST25TA IKEA Rothult
|
uint8_t tagType = 10; // 10 = ST25TA IKEA Rothult
|
||||||
tag_response_info_t *responses;
|
tag_response_info_t *responses;
|
||||||
uint32_t cuid = 0;
|
uint32_t cuid = 0;
|
||||||
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
|
|
||||||
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
|
|
||||||
uint8_t pages = 0;
|
uint8_t pages = 0;
|
||||||
|
|
||||||
// command buffers
|
// command buffers
|
||||||
|
@ -193,7 +191,7 @@ void RunMod(void) {
|
||||||
|
|
||||||
memcpy(data, stuid, sizeof(stuid));
|
memcpy(data, stuid, sizeof(stuid));
|
||||||
|
|
||||||
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) {
|
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");
|
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");
|
||||||
|
@ -371,7 +369,7 @@ void RunMod(void) {
|
||||||
|
|
||||||
memcpy(data, stuid, sizeof(stuid));
|
memcpy(data, stuid, sizeof(stuid));
|
||||||
|
|
||||||
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) {
|
if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");
|
DbpString(_YELLOW_("!!") "Error initializing the simulation process!");
|
||||||
|
|
|
@ -99,6 +99,7 @@ int tearoff_hook(void) {
|
||||||
if (g_tearoff_enabled) {
|
if (g_tearoff_enabled) {
|
||||||
if (g_tearoff_delay_us == 0) {
|
if (g_tearoff_delay_us == 0) {
|
||||||
Dbprintf(_RED_("No tear-off delay configured!"));
|
Dbprintf(_RED_("No tear-off delay configured!"));
|
||||||
|
g_tearoff_enabled = false;
|
||||||
return PM3_SUCCESS; // SUCCESS = the hook didn't do anything
|
return PM3_SUCCESS; // SUCCESS = the hook didn't do anything
|
||||||
}
|
}
|
||||||
SpinDelayUsPrecision(g_tearoff_delay_us);
|
SpinDelayUsPrecision(g_tearoff_delay_us);
|
||||||
|
@ -2229,6 +2230,10 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||||
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
|
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CMD_HF_ICLASS_TEARBL: {
|
||||||
|
iClass_TearBlock((iclass_tearblock_req_t *)packet->data.asBytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_HFSNIFF
|
#ifdef WITH_HFSNIFF
|
||||||
|
|
595
armsrc/iclass.c
595
armsrc/iclass.c
|
@ -38,6 +38,7 @@
|
||||||
#include "iso15693.h"
|
#include "iso15693.h"
|
||||||
#include "iclass_cmd.h" // iclass_card_select_t struct
|
#include "iclass_cmd.h" // iclass_card_select_t struct
|
||||||
#include "i2c.h" // i2c defines (SIM module access)
|
#include "i2c.h" // i2c defines (SIM module access)
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
uint8_t get_pagemap(const picopass_hdr_t *hdr) {
|
uint8_t get_pagemap(const picopass_hdr_t *hdr) {
|
||||||
return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
|
return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
|
||||||
|
@ -1244,7 +1245,6 @@ static bool iclass_send_cmd_with_retries(uint8_t *cmd, size_t cmdsize, uint8_t *
|
||||||
while (tries-- > 0) {
|
while (tries-- > 0) {
|
||||||
|
|
||||||
iclass_send_as_reader(cmd, cmdsize, start_time, eof_time, shallow_mod);
|
iclass_send_as_reader(cmd, cmdsize, start_time, eof_time, shallow_mod);
|
||||||
|
|
||||||
if (resp == NULL) {
|
if (resp == NULL) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1582,8 +1582,9 @@ bool iclass_read_block(uint16_t blockno, uint8_t *data, uint32_t *start_time, ui
|
||||||
uint8_t c[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
|
uint8_t c[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
|
||||||
AddCrc(c + 1, 1);
|
AddCrc(c + 1, 1);
|
||||||
bool isOK = iclass_send_cmd_with_retries(c, sizeof(c), resp, sizeof(resp), 10, 2, start_time, ICLASS_READER_TIMEOUT_OTHERS, eof_time, shallow_mod);
|
bool isOK = iclass_send_cmd_with_retries(c, sizeof(c), resp, sizeof(resp), 10, 2, start_time, ICLASS_READER_TIMEOUT_OTHERS, eof_time, shallow_mod);
|
||||||
if (isOK)
|
if (isOK) {
|
||||||
memcpy(data, resp, 8);
|
memcpy(data, resp, 8);
|
||||||
|
}
|
||||||
return isOK;
|
return isOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1780,13 +1781,13 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac,
|
||||||
}
|
}
|
||||||
} else if (blockno == 3 || blockno == 4) {
|
} else if (blockno == 3 || blockno == 4) {
|
||||||
// check response. Key updates always return 0xffffffffffffffff
|
// check response. Key updates always return 0xffffffffffffffff
|
||||||
uint8_t all_ff[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
uint8_t all_ff[PICOPASS_BLOCK_SIZE] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||||
if (memcmp(all_ff, resp, 8)) {
|
if (memcmp(all_ff, resp, PICOPASS_BLOCK_SIZE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check response. All other updates return unchanged data
|
// check response. All other updates return unchanged data
|
||||||
if (memcmp(data, resp, 8)) {
|
if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1794,6 +1795,28 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool iclass_writeblock_sp(uint8_t blockno, uint8_t *data, uint8_t *mac, bool shallow_mod, uint32_t *start_time, uint32_t *eof_time) {
|
||||||
|
|
||||||
|
// write command: cmd, 1 blockno, 8 data, 4 mac
|
||||||
|
uint8_t write[14] = { 0x80 | ICLASS_CMD_UPDATE, blockno };
|
||||||
|
uint8_t write_len = 14;
|
||||||
|
memcpy(write + 2, data, 8);
|
||||||
|
memcpy(write + 10, mac, 4);
|
||||||
|
|
||||||
|
uint8_t resp[10] = {0};
|
||||||
|
bool isOK = iclass_send_cmd_with_retries(write, write_len, resp, sizeof(resp), 10, 3, start_time, ICLASS_READER_TIMEOUT_UPDATE, eof_time, shallow_mod);
|
||||||
|
if (isOK == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check response. All other updates return unchanged data
|
||||||
|
if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// turn off afterwards
|
// turn off afterwards
|
||||||
void iClass_WriteBlock(uint8_t *msg) {
|
void iClass_WriteBlock(uint8_t *msg) {
|
||||||
|
|
||||||
|
@ -1829,7 +1852,7 @@ void iClass_WriteBlock(uint8_t *msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// new block data
|
// new block data
|
||||||
memcpy(write + 2, payload->data, 8);
|
memcpy(write + 2, payload->data, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
uint8_t pagemap = get_pagemap(&hdr);
|
uint8_t pagemap = get_pagemap(&hdr);
|
||||||
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||||
|
@ -1847,7 +1870,7 @@ void iClass_WriteBlock(uint8_t *msg) {
|
||||||
// Secure tags uses MAC
|
// Secure tags uses MAC
|
||||||
uint8_t wb[9];
|
uint8_t wb[9];
|
||||||
wb[0] = payload->req.blockno;
|
wb[0] = payload->req.blockno;
|
||||||
memcpy(wb + 1, payload->data, 8);
|
memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
if (payload->req.use_credit_key)
|
if (payload->req.use_credit_key)
|
||||||
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
||||||
|
@ -2074,6 +2097,421 @@ out:
|
||||||
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
|
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void iclass_cmp_print(uint8_t *b1, uint8_t *b2, const char *header1, const char *header2) {
|
||||||
|
|
||||||
|
char line1[240] = {0};
|
||||||
|
char line2[240] = {0};
|
||||||
|
|
||||||
|
strcat(line1, header1);
|
||||||
|
strcat(line2, header2);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < PICOPASS_BLOCK_SIZE; i++) {
|
||||||
|
|
||||||
|
int l1 = strlen(line1);
|
||||||
|
int l2 = strlen(line2);
|
||||||
|
|
||||||
|
uint8_t hi1 = NIBBLE_HIGH(b1[i]);
|
||||||
|
uint8_t low1 = NIBBLE_LOW(b1[i]);
|
||||||
|
|
||||||
|
uint8_t hi2 = NIBBLE_HIGH(b2[i]);
|
||||||
|
uint8_t low2 = NIBBLE_LOW(b2[i]);
|
||||||
|
|
||||||
|
if (hi1 != hi2) {
|
||||||
|
sprintf(line1 + l1, _RED_("%1X"), hi1);
|
||||||
|
sprintf(line2 + l2, _GREEN_("%1X"), hi2);
|
||||||
|
} else {
|
||||||
|
sprintf(line1 + l1, "%1X", hi1);
|
||||||
|
sprintf(line2 + l2, "%1X", hi2);
|
||||||
|
}
|
||||||
|
|
||||||
|
l1 = strlen(line1);
|
||||||
|
l2 = strlen(line2);
|
||||||
|
|
||||||
|
if (low1 != low2) {
|
||||||
|
sprintf(line1 + l1, _RED_("%1X"), low1);
|
||||||
|
sprintf(line2 + l2, _GREEN_("%1X"), low2);
|
||||||
|
} else {
|
||||||
|
sprintf(line1 + l1, "%1X", low1);
|
||||||
|
sprintf(line2 + l2, "%1X", low2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DbpString(line1);
|
||||||
|
DbpString(line2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iClass_TearBlock(iclass_tearblock_req_t *msg) {
|
||||||
|
|
||||||
|
if (msg == NULL) {
|
||||||
|
reply_ng(CMD_HF_ICLASS_TEARBL, PM3_ESOFT, NULL, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// local variable copies
|
||||||
|
int tear_start = msg->tear_start;
|
||||||
|
int tear_end = msg->tear_end;
|
||||||
|
int tear_inc = msg->increment;
|
||||||
|
int tear_loop = msg->tear_loop;
|
||||||
|
|
||||||
|
int loop_count = 0;
|
||||||
|
|
||||||
|
uint32_t start_time = 0;
|
||||||
|
uint32_t eof_time = 0;
|
||||||
|
|
||||||
|
int isok = PM3_SUCCESS;
|
||||||
|
|
||||||
|
uint8_t data[8] = {0};
|
||||||
|
memcpy(data, msg->data, sizeof(data));
|
||||||
|
|
||||||
|
uint8_t mac[4] = {0};
|
||||||
|
memcpy(mac, msg->mac, sizeof(mac));
|
||||||
|
|
||||||
|
picopass_hdr_t hdr = {0};
|
||||||
|
iclass_auth_req_t req = {
|
||||||
|
.blockno = msg->req.blockno,
|
||||||
|
.do_auth = msg->req.do_auth,
|
||||||
|
.send_reply = msg->req.send_reply,
|
||||||
|
.shallow_mod = msg->req.shallow_mod,
|
||||||
|
.use_credit_key = msg->req.use_credit_key,
|
||||||
|
.use_elite = msg->req.use_elite,
|
||||||
|
.use_raw = msg->req.use_raw,
|
||||||
|
.use_replay = msg->req.use_replay
|
||||||
|
};
|
||||||
|
memcpy(req.key, msg->req.key, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
|
LED_A_ON();
|
||||||
|
Iso15693InitReader();
|
||||||
|
|
||||||
|
// save old debug log level
|
||||||
|
int oldbg = g_dbglevel;
|
||||||
|
|
||||||
|
// no debug logging please
|
||||||
|
g_dbglevel = DBG_NONE;
|
||||||
|
|
||||||
|
// select
|
||||||
|
bool res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
|
||||||
|
if (res == false) {
|
||||||
|
DbpString(_RED_("Failed to select iClass tag"));
|
||||||
|
isok = PM3_ECARDEXCHANGE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, mac);
|
||||||
|
if (res == false) {
|
||||||
|
DbpString(_RED_("Failed to authenticate with iClass tag"));
|
||||||
|
isok = PM3_ECARDEXCHANGE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data_read_orig[PICOPASS_BLOCK_SIZE] = {0};
|
||||||
|
|
||||||
|
// read block
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
res = iclass_read_block(req.blockno, data_read_orig, &start_time, &eof_time, req.shallow_mod);
|
||||||
|
if (res == false) {
|
||||||
|
Dbprintf("Failed to read block %u", req.blockno);
|
||||||
|
isok = PM3_ECARDEXCHANGE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase_phase = false;
|
||||||
|
bool read_ok = false;
|
||||||
|
|
||||||
|
// static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
uint8_t ff_data[PICOPASS_BLOCK_SIZE] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
uint8_t data_read[PICOPASS_BLOCK_SIZE] = {0};
|
||||||
|
|
||||||
|
// create READ command
|
||||||
|
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, req.blockno, 0x00, 0x00};
|
||||||
|
AddCrc(cmd_read + 1, 1);
|
||||||
|
|
||||||
|
// create WRITE COMMAND and new block data
|
||||||
|
uint8_t cmd_write[14] = { 0x80 | ICLASS_CMD_UPDATE, req.blockno };
|
||||||
|
uint8_t cmd_write_len = 14;
|
||||||
|
memcpy(cmd_write + 2, data, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
|
uint8_t pagemap = get_pagemap(&hdr);
|
||||||
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||||
|
// Unsecured tags uses CRC16, but don't include the UPDATE operation code
|
||||||
|
// byte0 = update op
|
||||||
|
// byte1 = block no
|
||||||
|
// byte2..9 = new block data
|
||||||
|
AddCrc(cmd_write + 1, 9);
|
||||||
|
cmd_write_len -= 2;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (req.use_replay) {
|
||||||
|
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||||
|
} else {
|
||||||
|
// Secure tags uses MAC
|
||||||
|
uint8_t wb[9];
|
||||||
|
wb[0] = req.blockno;
|
||||||
|
memcpy(wb + 1, data, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (req.use_credit_key)
|
||||||
|
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
||||||
|
else
|
||||||
|
doMAC_N(wb, sizeof(wb), hdr.key_d, mac);
|
||||||
|
|
||||||
|
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
while ((tear_start <= tear_end) && (read_ok == false)) {
|
||||||
|
|
||||||
|
if (BUTTON_PRESS() || data_available()) {
|
||||||
|
isok = PM3_EOPABORTED;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set tear off trigger
|
||||||
|
g_tearoff_enabled = true;
|
||||||
|
g_tearoff_delay_us = (tear_start & 0xFFFF);
|
||||||
|
|
||||||
|
if (tear_loop > 1) {
|
||||||
|
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us - " _YELLOW_("%3u") " iter", tear_start, tear_end, loop_count + 1);
|
||||||
|
} else {
|
||||||
|
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us", tear_start, tear_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write block
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod);
|
||||||
|
|
||||||
|
tearoff_hook();
|
||||||
|
|
||||||
|
switch_off();
|
||||||
|
|
||||||
|
// start reading block
|
||||||
|
|
||||||
|
// reinit
|
||||||
|
Iso15693InitReader();
|
||||||
|
|
||||||
|
// select tag
|
||||||
|
res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
|
||||||
|
if (res == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip authentication for config and e-purse blocks (1,2)
|
||||||
|
if (req.blockno > 2) {
|
||||||
|
|
||||||
|
// authenticate
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, NULL);
|
||||||
|
if (res == false) {
|
||||||
|
DbpString("Failed to authenticate after tear");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read again and keep field on
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
res = iclass_read_block(req.blockno, data_read, &start_time, &eof_time, req.shallow_mod);
|
||||||
|
if (res == false) {
|
||||||
|
DbpString("Failed to read block after tear");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
bool tear_success = true;
|
||||||
|
|
||||||
|
if (memcmp(data_read, data, PICOPASS_BLOCK_SIZE) != 0) {
|
||||||
|
tear_success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tear_success == false) &&
|
||||||
|
(memcmp(data_read, zeros, PICOPASS_BLOCK_SIZE) != 0) &&
|
||||||
|
(memcmp(data_read, data_read_orig, PICOPASS_BLOCK_SIZE) != 0)) {
|
||||||
|
|
||||||
|
// tearoff succeeded (partially)
|
||||||
|
|
||||||
|
if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 &&
|
||||||
|
memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) {
|
||||||
|
|
||||||
|
if(erase_phase == false){
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_CYAN_("Erase phase hit... ALL ONES"));
|
||||||
|
|
||||||
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||||
|
}
|
||||||
|
erase_phase = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (erase_phase) {
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_MAGENTA_("Tearing! Write phase (post erase)"));
|
||||||
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||||
|
} else {
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_CYAN_("Tearing! unknown phase"));
|
||||||
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shall we exit? well it depends on some things.
|
||||||
|
bool goto_out = false;
|
||||||
|
|
||||||
|
if (req.blockno == 2) {
|
||||||
|
if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 && memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) {
|
||||||
|
DbpString("");
|
||||||
|
Dbprintf("E-purse has been teared ( %s )", _GREEN_("ok"));
|
||||||
|
isok = PM3_SUCCESS;
|
||||||
|
goto_out = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.blockno == 1) {
|
||||||
|
|
||||||
|
// if more OTP bits set..
|
||||||
|
if (data_read[1] > data_read_orig[1] ||
|
||||||
|
data_read[2] > data_read_orig[2]) {
|
||||||
|
|
||||||
|
|
||||||
|
// step 4 if bits changed attempt to write the new bits to the tag
|
||||||
|
if (data_read[7] == 0xBC) {
|
||||||
|
data_read[7] = 0xAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare WRITE command
|
||||||
|
cmd_write_len = 14;
|
||||||
|
memcpy(cmd_write + 2, data_read, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||||
|
// Unsecured tags uses CRC16, but don't include the UPDATE operation code
|
||||||
|
// byte0 = update op
|
||||||
|
// byte1 = block no
|
||||||
|
// byte2..9 = new block data
|
||||||
|
AddCrc(cmd_write + 1, 9);
|
||||||
|
cmd_write_len -= 2;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (req.use_replay) {
|
||||||
|
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||||
|
} else {
|
||||||
|
// Secure tags uses MAC
|
||||||
|
uint8_t wb[9];
|
||||||
|
wb[0] = req.blockno;
|
||||||
|
memcpy(wb + 1, data_read, PICOPASS_BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (req.use_credit_key)
|
||||||
|
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
||||||
|
else
|
||||||
|
doMAC_N(wb, sizeof(wb), hdr.key_d, mac);
|
||||||
|
|
||||||
|
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write block
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
|
iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod);
|
||||||
|
|
||||||
|
uint16_t resp_len = 0;
|
||||||
|
uint8_t resp[ICLASS_BUFFER_SIZE] = {0};
|
||||||
|
res = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true, &resp_len);
|
||||||
|
if (res == PM3_SUCCESS && resp_len == 10) {
|
||||||
|
Dbprintf("Wrote to block");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_off();
|
||||||
|
|
||||||
|
DbpString("");
|
||||||
|
DbpString("More OTP bits got set!!!");
|
||||||
|
|
||||||
|
Iso15693InitReader();
|
||||||
|
|
||||||
|
// select tag, during which we read block1
|
||||||
|
res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
|
||||||
|
if (res) {
|
||||||
|
|
||||||
|
if (memcmp(&hdr.conf, cmd_write + 2, PICOPASS_BLOCK_SIZE) == 0) {
|
||||||
|
Dbprintf("Stabilize the bits ( "_GREEN_("ok") " )");
|
||||||
|
} else {
|
||||||
|
Dbprintf("Stabilize the bits ( "_RED_("failed") " )");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isok = PM3_SUCCESS;
|
||||||
|
goto_out = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_read[0] != data_read_orig[0]) {
|
||||||
|
DbpString("");
|
||||||
|
Dbprintf("Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]);
|
||||||
|
isok = PM3_SUCCESS;
|
||||||
|
goto_out = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_read[7] != data_read_orig[7]) {
|
||||||
|
DbpString("");
|
||||||
|
Dbprintf("Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]);
|
||||||
|
|
||||||
|
const char *flag_names[8] = {
|
||||||
|
"RA",
|
||||||
|
"Fprod0",
|
||||||
|
"Fprod1",
|
||||||
|
"Crypt0 (*1)",
|
||||||
|
"Crypt1 (*0)",
|
||||||
|
"Coding0",
|
||||||
|
"Coding1",
|
||||||
|
"Fpers (*1)"
|
||||||
|
};
|
||||||
|
Dbprintf(_YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed");
|
||||||
|
Dbprintf("---------------------------------------");
|
||||||
|
for (int i = 7; i >= 0; --i) {
|
||||||
|
int bit1 = (data_read_orig[7] >> i) & 1;
|
||||||
|
int bit2 = (data_read[7] >> i) & 1;
|
||||||
|
Dbprintf("%-11s %-10d %-10d", flag_names[i], bit1, bit2);
|
||||||
|
}
|
||||||
|
|
||||||
|
isok = PM3_SUCCESS;
|
||||||
|
goto_out = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goto_out) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tearoff succeeded with expected values, which is unlikely
|
||||||
|
if (tear_success) {
|
||||||
|
read_ok = true;
|
||||||
|
tear_success = true;
|
||||||
|
DbpString("");
|
||||||
|
DbpString("tear success (expected values)!");
|
||||||
|
}
|
||||||
|
|
||||||
|
loop_count++;
|
||||||
|
|
||||||
|
// increase tear off delay
|
||||||
|
if (loop_count == tear_loop) {
|
||||||
|
tear_start += tear_inc;
|
||||||
|
loop_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
|
||||||
|
switch_off();
|
||||||
|
|
||||||
|
// reset tear off trigger
|
||||||
|
g_tearoff_enabled = false;
|
||||||
|
|
||||||
|
// restore debug message levels
|
||||||
|
g_dbglevel = oldbg;
|
||||||
|
|
||||||
|
if (msg->req.send_reply) {
|
||||||
|
reply_ng(CMD_HF_ICLASS_TEARBL, isok, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void iClass_Restore(iclass_restore_req_t *msg) {
|
void iClass_Restore(iclass_restore_req_t *msg) {
|
||||||
|
|
||||||
// sanitation
|
// sanitation
|
||||||
|
@ -2204,6 +2642,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
int bits_found = -1;
|
int bits_found = -1;
|
||||||
bool recovered = false;
|
bool recovered = false;
|
||||||
bool completed = false;
|
bool completed = false;
|
||||||
|
bool interrupted = false;
|
||||||
uint8_t div_key2[8] = {0};
|
uint8_t div_key2[8] = {0};
|
||||||
uint32_t eof_time = 0;
|
uint32_t eof_time = 0;
|
||||||
uint32_t start_time = 0;
|
uint32_t start_time = 0;
|
||||||
|
@ -2225,7 +2664,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
};
|
};
|
||||||
|
|
||||||
LED_A_ON();
|
LED_A_ON();
|
||||||
DbpString(_RED_("Interrupting this process will render the card unusable!"));
|
DbpString(_RED_("Interrupting this process may render the card unusable!"));
|
||||||
memcpy(div_key2, msg->nfa, 8);
|
memcpy(div_key2, msg->nfa, 8);
|
||||||
|
|
||||||
//START LOOP
|
//START LOOP
|
||||||
|
@ -2243,8 +2682,26 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
uint8_t mac2[4] = {0};
|
uint8_t mac2[4] = {0};
|
||||||
picopass_hdr_t hdr = {0};
|
picopass_hdr_t hdr = {0};
|
||||||
bool res = false;
|
bool res = false;
|
||||||
|
int status_message = 0;
|
||||||
|
|
||||||
while (!card_select || !card_auth) {
|
while (!card_select || !card_auth) {
|
||||||
|
|
||||||
|
if (BUTTON_PRESS() || loops > msg->loop) {
|
||||||
|
if(loops > msg->loop){
|
||||||
|
completed = true;
|
||||||
|
}else{
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->test) {
|
||||||
|
Dbprintf(_YELLOW_("*Cycled Reader*") " TEST Index - Loops: "_YELLOW_("%3d / %3d") " *", loops, msg->loop);
|
||||||
|
}else if (msg->debug){
|
||||||
|
Dbprintf(_YELLOW_("*Cycled Reader*") " Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " *", index, loops, msg->loop);
|
||||||
|
}else{
|
||||||
|
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Index: "_CYAN_("%3d")" Loops: "_YELLOW_("%3d / %3d")" ", index, loops, msg->loop);
|
||||||
|
}
|
||||||
Iso15693InitReader(); //has to be at the top as it starts tracing
|
Iso15693InitReader(); //has to be at the top as it starts tracing
|
||||||
if (!msg->debug) {
|
if (!msg->debug) {
|
||||||
set_tracing(false); //disable tracing to prevent crashes - set to true for debugging
|
set_tracing(false); //disable tracing to prevent crashes - set to true for debugging
|
||||||
|
@ -2253,18 +2710,11 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
clear_trace(); //if we're debugging better to clear the trace but do it only on the first loop
|
clear_trace(); //if we're debugging better to clear the trace but do it only on the first loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg->test) {
|
|
||||||
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- TEST Index - Loops: "_YELLOW_("%3d / %3d") " --------------*", loops, msg->loop);
|
|
||||||
} else {
|
|
||||||
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " --------------*", index, loops, msg->loop);
|
|
||||||
}
|
|
||||||
//Step0 Card Select Routine
|
//Step0 Card Select Routine
|
||||||
eof_time = 0; //reset eof time
|
eof_time = 0; //reset eof time
|
||||||
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
|
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
|
||||||
if (res == false) {
|
if (res) {
|
||||||
DbpString(_RED_("Unable to select card after reader cycle! Retrying..."));
|
status_message = 1; //card select successful
|
||||||
} else {
|
|
||||||
DbpString(_GREEN_("Card selected successfully!"));
|
|
||||||
card_select = true;
|
card_select = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2273,10 +2723,8 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
memcpy(original_mac, msg->req.key, 8);
|
memcpy(original_mac, msg->req.key, 8);
|
||||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
||||||
if (res == false) {
|
if (res) {
|
||||||
DbpString(_RED_("Unable to authenticate on AA1 using macs! Retrying..."));
|
status_message = 2; //authentication with AA1 macs successful
|
||||||
} else {
|
|
||||||
DbpString(_GREEN_("AA1 authentication with macs successful!"));
|
|
||||||
card_auth = true;
|
card_auth = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2285,6 +2733,7 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
switch_off();
|
switch_off();
|
||||||
}
|
}
|
||||||
if (reinit_tentatives == 5) {
|
if (reinit_tentatives == 5) {
|
||||||
|
DbpString("");
|
||||||
DbpString(_RED_("Unable to select or authenticate with card multiple times! Stopping."));
|
DbpString(_RED_("Unable to select or authenticate with card multiple times! Stopping."));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2292,23 +2741,22 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
|
|
||||||
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
|
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
|
||||||
uint8_t blockno = 24;
|
uint8_t blockno = 24;
|
||||||
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
|
|
||||||
AddCrc(cmd_read + 1, 1);
|
|
||||||
int priv_esc_tries = 0;
|
int priv_esc_tries = 0;
|
||||||
bool priv_esc = false;
|
bool priv_esc = false;
|
||||||
while (!priv_esc) {
|
while (!priv_esc) {
|
||||||
//The privilege escalation is done with a readcheck and not just a normal read!
|
//The privilege escalation is done with a readcheck and not just a normal read!
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
|
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
|
||||||
// expect a 8-byte response here
|
// expect a 8-byte response here
|
||||||
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
||||||
if (res2 != PM3_SUCCESS || resp_len != 8) {
|
if (res2 != PM3_SUCCESS || resp_len != 8) {
|
||||||
DbpString(_YELLOW_("Privilege Escalation -> ")_RED_("Read failed! Trying again..."));
|
|
||||||
priv_esc_tries++;
|
priv_esc_tries++;
|
||||||
} else {
|
} else {
|
||||||
DbpString(_YELLOW_("Privilege Escalation -> ")_GREEN_("Response OK!"));
|
status_message = 3; //privilege escalation successful
|
||||||
priv_esc = true;
|
priv_esc = true;
|
||||||
}
|
}
|
||||||
if (priv_esc_tries == 5) {
|
if (priv_esc_tries == 5) {
|
||||||
|
DbpString("");
|
||||||
DbpString(_RED_("Unable to complete privilege escalation! Stopping."));
|
DbpString(_RED_("Unable to complete privilege escalation! Stopping."));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2327,37 +2775,37 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
wb[0] = blockno;
|
wb[0] = blockno;
|
||||||
memcpy(wb + 1, genkeyblock, 8);
|
memcpy(wb + 1, genkeyblock, 8);
|
||||||
doMAC_N(wb, sizeof(wb), div_key2, mac2);
|
doMAC_N(wb, sizeof(wb), div_key2, mac2);
|
||||||
bool use_mac = true;
|
|
||||||
bool written = false;
|
bool written = false;
|
||||||
bool write_error = false;
|
bool write_error = false;
|
||||||
while (written == false && write_error == false) {
|
while (written == false && write_error == false) {
|
||||||
//Step5 Perform Write
|
//Step5 Perform Write
|
||||||
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
|
if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) {
|
||||||
DbpString("Wrote key: ");
|
status_message = 4; //wrote new key on the card - unverified
|
||||||
Dbhexdump(8, genkeyblock, false);
|
|
||||||
}
|
}
|
||||||
//Reset cypher state
|
//Reset cypher state
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
||||||
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
|
||||||
//try to authenticate with the original mac to verify the write happened
|
//try to authenticate with the original mac to verify the write happened
|
||||||
memcpy(msg->req.key, original_mac, 8);
|
memcpy(msg->req.key, original_mac, 8);
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
||||||
if (msg->test) {
|
if (msg->test) {
|
||||||
if (res != true) {
|
if (res) {
|
||||||
DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***"));
|
DbpString("");
|
||||||
goto out;
|
|
||||||
} else {
|
|
||||||
DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***"));
|
DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***"));
|
||||||
completed = true;
|
completed = true;
|
||||||
goto out;
|
goto out;
|
||||||
|
} else {
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***"));
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (res != true) {
|
if (res) {
|
||||||
DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!"));
|
write_error = true; //failed to update the key, the card's key is the original one
|
||||||
written = true;
|
|
||||||
} else {
|
} else {
|
||||||
DbpString("Write Operation : "_RED_("FAILED! Card Key is the Original. Retrying..."));
|
status_message = 5; //verified the card key was updated to the new one
|
||||||
write_error = true;
|
written = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2365,16 +2813,14 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
if (!write_error) {
|
if (!write_error) {
|
||||||
//Step6 Perform 8 authentication attempts + 1 to verify if we found the weak key
|
//Step6 Perform 8 authentication attempts + 1 to verify if we found the weak key
|
||||||
for (int i = 0; i < 8 ; ++i) {
|
for (int i = 0; i < 8 ; ++i) {
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
||||||
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
|
||||||
//need to craft the authentication payload accordingly
|
//need to craft the authentication payload accordingly
|
||||||
memcpy(msg->req.key, iclass_mac_table[i], 8);
|
memcpy(msg->req.key, iclass_mac_table[i], 8);
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter
|
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
bits_found = i;
|
bits_found = i;
|
||||||
DbpString(_RED_("--------------------------------------------------------"));
|
|
||||||
Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found);
|
|
||||||
DbpString(_RED_("--------------------------------------------------------"));
|
|
||||||
recovered = true;
|
recovered = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2384,37 +2830,36 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
uint8_t revert_retries = 0;
|
uint8_t revert_retries = 0;
|
||||||
while (!reverted) {
|
while (!reverted) {
|
||||||
//Regain privilege escalation with a readcheck
|
//Regain privilege escalation with a readcheck
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
|
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
|
||||||
// TODO: check result
|
// TODO: check result
|
||||||
GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
//GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
DbpString(_YELLOW_("Attempting to restore the original key. "));
|
if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) {
|
||||||
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
|
status_message = 6; //restore of original key successful but unverified
|
||||||
DbpString("Restore of Original Key "_GREEN_("successful."));
|
|
||||||
} else {
|
|
||||||
DbpString("Restore of Original Key " _RED_("failed."));
|
|
||||||
}
|
}
|
||||||
DbpString(_YELLOW_("Verifying Key Restore..."));
|
|
||||||
//Do a readcheck first to reset the cypher state
|
//Do a readcheck first to reset the cypher state
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
|
||||||
// TODO: check result
|
// TODO: check result
|
||||||
GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
//GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
|
||||||
|
|
||||||
//need to craft the authentication payload accordingly
|
//need to craft the authentication payload accordingly
|
||||||
memcpy(msg->req.key, original_mac, 8);
|
memcpy(msg->req.key, original_mac, 8);
|
||||||
|
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||||
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
DbpString("Restore of Original Key "_GREEN_("VERIFIED! Card is usable again."));
|
status_message = 7; //restore of original key verified - card usable again
|
||||||
reverted = true;
|
reverted = true;
|
||||||
if (recovered) {
|
if (recovered) {
|
||||||
goto restore;
|
goto restore;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
DbpString("Restore of Original Key "_RED_("VERIFICATION FAILED! Trying again..."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
revert_retries++;
|
revert_retries++;
|
||||||
if (revert_retries >= 7) { //must always be an odd number!
|
if (revert_retries >= 7) { //must always be an odd number!
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_CYAN_("Last Written Key: "));
|
||||||
|
Dbhexdump(8, genkeyblock, false);
|
||||||
Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries);
|
Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2422,11 +2867,35 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loops >= msg->loop) {
|
if(msg->debug){
|
||||||
completed = true;
|
if(status_message >= 1){
|
||||||
goto out;
|
DbpString("");
|
||||||
|
DbpString("Card Select:............."_GREEN_("Ok!"));
|
||||||
|
}
|
||||||
|
if(status_message >= 2){
|
||||||
|
DbpString("AA1 macs authentication:."_GREEN_("Ok!"));
|
||||||
|
}
|
||||||
|
if(status_message >= 3){
|
||||||
|
DbpString("Privilege Escalation:...."_GREEN_("Ok!"));
|
||||||
|
}
|
||||||
|
if(status_message >= 4){
|
||||||
|
DbpString("Wrote key: ");
|
||||||
|
Dbhexdump(8, genkeyblock, false);
|
||||||
|
}
|
||||||
|
if(status_message >= 5){
|
||||||
|
DbpString("Key Update:.............."_GREEN_("Verified!"));
|
||||||
|
}
|
||||||
|
if(status_message >= 6){
|
||||||
|
DbpString("Original Key Restore:...."_GREEN_("Ok!"));
|
||||||
|
}
|
||||||
|
if(status_message >= 7){
|
||||||
|
DbpString("Original Key Restore:...."_GREEN_("Verified!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!write_error) { //if there was a write error, re-run the loop for the same key index
|
|
||||||
|
if (write_error && (msg->debug || msg->test)) { //if there was a write error, re-run the loop for the same key index
|
||||||
|
DbpString("Loop Error: "_RED_("Repeating Loop!"));
|
||||||
|
}else{
|
||||||
loops++;
|
loops++;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
@ -2442,6 +2911,10 @@ restore:
|
||||||
partialkey[i] = genkeyblock[i] ^ bits_found;
|
partialkey[i] = genkeyblock[i] ^ bits_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Print the bits decimal value
|
||||||
|
DbpString("");
|
||||||
|
DbpString(_RED_("--------------------------------------------------------"));
|
||||||
|
Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found);
|
||||||
//Print the 24 bits found from k1
|
//Print the 24 bits found from k1
|
||||||
DbpString(_RED_("--------------------------------------------------------"));
|
DbpString(_RED_("--------------------------------------------------------"));
|
||||||
DbpString(_RED_("SUCCESS! Raw Key Partial Bytes: "));
|
DbpString(_RED_("SUCCESS! Raw Key Partial Bytes: "));
|
||||||
|
@ -2456,6 +2929,8 @@ out:
|
||||||
switch_off();
|
switch_off();
|
||||||
if (completed) {
|
if (completed) {
|
||||||
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EINVARG, NULL, 0);
|
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EINVARG, NULL, 0);
|
||||||
|
} else if (interrupted){
|
||||||
|
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EOPABORTED, NULL, 0);
|
||||||
} else {
|
} else {
|
||||||
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
|
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,4 +72,5 @@ uint8_t get_pagemap(const picopass_hdr_t *hdr);
|
||||||
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
|
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
|
||||||
|
|
||||||
void iClass_Recover(iclass_recover_req_t *msg);
|
void iClass_Recover(iclass_recover_req_t *msg);
|
||||||
|
void iClass_TearBlock(iclass_tearblock_req_t *msg);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1184,7 +1184,7 @@ bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_
|
||||||
|
|
||||||
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
||||||
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
|
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
|
||||||
uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages) {
|
uint32_t *cuid, uint8_t *pages) {
|
||||||
uint8_t sak = 0;
|
uint8_t sak = 0;
|
||||||
// The first response contains the ATQA (note: bytes are transmitted in reverse order).
|
// The first response contains the ATQA (note: bytes are transmitted in reverse order).
|
||||||
static uint8_t rATQA[2] = { 0x00 };
|
static uint8_t rATQA[2] = { 0x00 };
|
||||||
|
@ -1231,14 +1231,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
||||||
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
|
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
|
||||||
*pages = MAX(mfu_header->pages, 15);
|
*pages = MAX(mfu_header->pages, 15);
|
||||||
|
|
||||||
// counters and tearing flags
|
// tearing flags
|
||||||
// for old dumps with all zero headers, we need to set default values.
|
// for old dumps with all zero headers, we need to set default values.
|
||||||
for (uint8_t i = 0; i < 3; i++) {
|
for (uint8_t i = 0; i < 3; i++) {
|
||||||
|
if (mfu_header->counter_tearing[i][3] == 0x00) {
|
||||||
counters[i] = le24toh(mfu_header->counter_tearing[i]);
|
mfu_header->counter_tearing[i][3] = 0xBD;
|
||||||
|
|
||||||
if (mfu_header->counter_tearing[i][3] != 0x00) {
|
|
||||||
tearings[i] = mfu_header->counter_tearing[i][3];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1286,14 +1283,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
||||||
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
|
mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr();
|
||||||
*pages = MAX(mfu_header->pages, 19);
|
*pages = MAX(mfu_header->pages, 19);
|
||||||
|
|
||||||
// counters and tearing flags
|
// tearing flags
|
||||||
// for old dumps with all zero headers, we need to set default values.
|
// for old dumps with all zero headers, we need to set default values.
|
||||||
for (uint8_t i = 0; i < 3; i++) {
|
for (uint8_t i = 0; i < 3; i++) {
|
||||||
|
if (mfu_header->counter_tearing[i][3] == 0x00) {
|
||||||
counters[i] = le24toh(mfu_header->counter_tearing[i]);
|
mfu_header->counter_tearing[i][3] = 0xBD;
|
||||||
|
|
||||||
if (mfu_header->counter_tearing[i][3] != 0x00) {
|
|
||||||
tearings[i] = mfu_header->counter_tearing[i][3];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1539,8 +1533,6 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
|
||||||
tag_response_info_t *responses;
|
tag_response_info_t *responses;
|
||||||
uint32_t cuid = 0;
|
uint32_t cuid = 0;
|
||||||
uint32_t nonce = 0;
|
uint32_t nonce = 0;
|
||||||
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
|
|
||||||
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
|
|
||||||
uint8_t pages = 0;
|
uint8_t pages = 0;
|
||||||
|
|
||||||
// Here, we collect CUID, block1, keytype1, NT1, NR1, AR1, CUID, block2, keytyp2, NT2, NR2, AR2
|
// Here, we collect CUID, block1, keytype1, NT1, NR1, AR1, CUID, block2, keytyp2, NT2, NR2, AR2
|
||||||
|
@ -1584,12 +1576,22 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
|
||||||
.modulation_n = 0
|
.modulation_n = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) {
|
if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, &pages) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mfu_dump_t *mfu_em_dump = NULL;
|
||||||
|
if (tagType == 2 || tagType == 7) {
|
||||||
|
mfu_em_dump = (mfu_dump_t *)BigBuf_get_EM_addr();
|
||||||
|
if (!mfu_em_dump) {
|
||||||
|
if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: Failed to get EM address for MFU/NTAG operations.");
|
||||||
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We need to listen to the high-frequency, peak-detected path.
|
// We need to listen to the high-frequency, peak-detected path.
|
||||||
iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN);
|
iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN);
|
||||||
|
|
||||||
|
@ -1870,8 +1872,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
|
||||||
// send NACK 0x0 == invalid argument
|
// send NACK 0x0 == invalid argument
|
||||||
EmSend4bit(CARD_NACK_IV);
|
EmSend4bit(CARD_NACK_IV);
|
||||||
} else {
|
} else {
|
||||||
uint8_t cmd[] = {0x00, 0x00, 0x00, 0x14, 0xa5};
|
uint8_t cmd[] = {0, 0, 0, 0x14, 0xa5};
|
||||||
htole24(counters[index], cmd);
|
memcpy(cmd, mfu_em_dump->counter_tearing[index], 3);
|
||||||
AddCrc14A(cmd, sizeof(cmd) - 2);
|
AddCrc14A(cmd, sizeof(cmd) - 2);
|
||||||
EmSendCmd(cmd, sizeof(cmd));
|
EmSendCmd(cmd, sizeof(cmd));
|
||||||
}
|
}
|
||||||
|
@ -1882,13 +1884,16 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
|
||||||
// send NACK 0x0 == invalid argument
|
// send NACK 0x0 == invalid argument
|
||||||
EmSend4bit(CARD_NACK_IV);
|
EmSend4bit(CARD_NACK_IV);
|
||||||
} else {
|
} else {
|
||||||
uint32_t val = le24toh(receivedCmd + 2) + counters[index];
|
uint32_t val = le24toh(mfu_em_dump->counter_tearing[index]); // get current counter value
|
||||||
|
val += le24toh(receivedCmd + 2); // increment in
|
||||||
|
|
||||||
// if new value + old value is bigger 24bits, fail
|
// if new value + old value is bigger 24bits, fail
|
||||||
if (val > 0xFFFFFF) {
|
if (val > 0xFFFFFF) {
|
||||||
// send NACK 0x4 == counter overflow
|
// send NACK 0x4 == counter overflow
|
||||||
EmSend4bit(CARD_NACK_NA);
|
EmSend4bit(CARD_NACK_NA);
|
||||||
} else {
|
} else {
|
||||||
counters[index] = val;
|
htole24(val, mfu_em_dump->counter_tearing[index]);
|
||||||
|
|
||||||
// send ACK
|
// send ACK
|
||||||
EmSend4bit(CARD_ACK);
|
EmSend4bit(CARD_ACK);
|
||||||
}
|
}
|
||||||
|
@ -1902,7 +1907,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin
|
||||||
EmSend4bit(CARD_NACK_IV);
|
EmSend4bit(CARD_NACK_IV);
|
||||||
} else {
|
} else {
|
||||||
uint8_t cmd[3] = {0, 0, 0};
|
uint8_t cmd[3] = {0, 0, 0};
|
||||||
cmd[0] = tearings[index];
|
cmd[0] = mfu_em_dump->counter_tearing[index][3];
|
||||||
AddCrc14A(cmd, sizeof(cmd) - 2);
|
AddCrc14A(cmd, sizeof(cmd) - 2);
|
||||||
EmSendCmd(cmd, sizeof(cmd));
|
EmSendCmd(cmd, sizeof(cmd));
|
||||||
}
|
}
|
||||||
|
@ -4091,8 +4096,6 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
|
||||||
uint8_t *getdata_response, size_t getdata_response_len) {
|
uint8_t *getdata_response, size_t getdata_response_len) {
|
||||||
tag_response_info_t *responses;
|
tag_response_info_t *responses;
|
||||||
uint32_t cuid = 0;
|
uint32_t cuid = 0;
|
||||||
uint32_t counters[3] = { 0x00, 0x00, 0x00 };
|
|
||||||
uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd };
|
|
||||||
uint8_t pages = 0;
|
uint8_t pages = 0;
|
||||||
|
|
||||||
// command buffers
|
// command buffers
|
||||||
|
@ -4133,7 +4136,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
|
||||||
.modulation_n = 0
|
.modulation_n = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) {
|
if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, &pages) == false) {
|
||||||
BigBuf_free_keep_EM();
|
BigBuf_free_keep_EM();
|
||||||
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0);
|
||||||
return;
|
return;
|
||||||
|
@ -4276,6 +4279,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
|
||||||
dynamic_response_info.response_n = 3 + offset;
|
dynamic_response_info.response_n = 3 + offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid,
|
||||||
|
|
||||||
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data,
|
||||||
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
|
uint8_t *ats, size_t ats_len, tag_response_info_t **responses,
|
||||||
uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages);
|
uint32_t *cuid, uint8_t *pages);
|
||||||
|
|
||||||
bool GetIso14443aCommandFromReader(uint8_t *received, uint16_t received_maxlen, uint8_t *par, int *len);
|
bool GetIso14443aCommandFromReader(uint8_t *received, uint16_t received_maxlen, uint8_t *par, int *len);
|
||||||
void iso14443a_antifuzz(uint32_t flags);
|
void iso14443a_antifuzz(uint32_t flags);
|
||||||
|
|
|
@ -2135,6 +2135,9 @@ static int iso14443b_select_picopass_card(picopass_hdr_t *hdr) {
|
||||||
static uint8_t act_all[] = { ICLASS_CMD_ACTALL };
|
static uint8_t act_all[] = { ICLASS_CMD_ACTALL };
|
||||||
static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY };
|
static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY };
|
||||||
static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 };
|
static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 };
|
||||||
|
|
||||||
|
// ICLASS_CMD_SELECT 0x81 tells ISO14443b/BPSK coding/106 kbits/s
|
||||||
|
// ICLASS_CMD_SELECT 0x41 tells ISO14443b/BPSK coding/423 kbits/s
|
||||||
uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||||
uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64};
|
uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64};
|
||||||
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 };
|
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 };
|
||||||
|
|
|
@ -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 *product_type_str = "";
|
||||||
const char *major_product_version_str = "";
|
const char *major_product_version_str = "";
|
||||||
const char *storage_size_str = "";
|
const char *storage_size_str = "";
|
||||||
|
|
||||||
if (version_hw_available) {
|
if (version_hw_available) {
|
||||||
|
|
||||||
switch (version_hw->product_type & 0x0F) {
|
switch (version_hw->product_type & 0x0F) {
|
||||||
case 0x1:
|
case 0x1: {
|
||||||
product_type_str = "MIFARE DESFire";
|
product_type_str = "MIFARE DESFire";
|
||||||
// special cases, override product_type_str when needed
|
// special cases, override product_type_str when needed
|
||||||
if (version_hw->product_type == 0x91) {
|
if (version_hw->product_type == 0x91) {
|
||||||
product_type_str = "Apple Wallet DESFire Applet";
|
product_type_str = "Apple Wallet DESFire Applet";
|
||||||
}
|
}
|
||||||
|
|
||||||
// general rule
|
// general rule
|
||||||
switch (version_hw->major_product_version & 0x0F) {
|
switch (version_hw->major_product_version & 0x0F) {
|
||||||
case 0x01:
|
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";
|
major_product_version_str = "EV3";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// special cases, override major_product_version_str when needed
|
// special cases, override major_product_version_str when needed
|
||||||
switch (version_hw->major_product_version) {
|
switch (version_hw->major_product_version) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
|
@ -1860,7 +1864,8 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x2:
|
}
|
||||||
|
case 0x2: {
|
||||||
product_type_str = "MIFARE Plus";
|
product_type_str = "MIFARE Plus";
|
||||||
switch (version_hw->major_product_version) {
|
switch (version_hw->major_product_version) {
|
||||||
case 0x11:
|
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";
|
major_product_version_str = "EV2";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
major_product_version_str = "Unknown";
|
major_product_version_str = "n/a";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x3:
|
}
|
||||||
|
case 0x3: {
|
||||||
product_type_str = "MIFARE Ultralight";
|
product_type_str = "MIFARE Ultralight";
|
||||||
switch (version_hw->major_product_version) {
|
switch (version_hw->major_product_version) {
|
||||||
case 0x01:
|
case 0x01: {
|
||||||
major_product_version_str = "EV1";
|
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;
|
break;
|
||||||
|
}
|
||||||
case 0x02:
|
case 0x02:
|
||||||
major_product_version_str = "Nano";
|
major_product_version_str = "Nano";
|
||||||
break;
|
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";
|
major_product_version_str = "AES";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
major_product_version_str = "Unknown";
|
major_product_version_str = "n/a";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x4:
|
}
|
||||||
|
case 0x4: {
|
||||||
product_type_str = "NTAG";
|
product_type_str = "NTAG";
|
||||||
switch (version_hw->major_product_version) {
|
switch (version_hw->major_product_version) {
|
||||||
case 0x01:
|
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";
|
major_product_version_str = "4xx";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
major_product_version_str = "Unknown";
|
major_product_version_str = "n/a";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x7:
|
}
|
||||||
|
case 0x7: {
|
||||||
product_type_str = "NTAG I2C";
|
product_type_str = "NTAG I2C";
|
||||||
break;
|
break;
|
||||||
case 0x8:
|
}
|
||||||
|
case 0x8: {
|
||||||
product_type_str = "MIFARE DESFire Light";
|
product_type_str = "MIFARE DESFire Light";
|
||||||
break;
|
break;
|
||||||
case 0x9:
|
}
|
||||||
|
case 0x9: {
|
||||||
product_type_str = "MIFARE Hospitality";
|
product_type_str = "MIFARE Hospitality";
|
||||||
switch (version_hw->major_product_version) {
|
switch (version_hw->major_product_version) {
|
||||||
case 0x01:
|
case 0x01:
|
||||||
major_product_version_str = "AES";
|
major_product_version_str = "AES";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
major_product_version_str = "Unknown";
|
major_product_version_str = "n/a";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
|
default: {
|
||||||
product_type_str = "Unknown NXP tag";
|
product_type_str = "Unknown NXP tag";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uint32_t size = 1 << (version_hw->storage_size >> 1);
|
|
||||||
static char size_str[16];
|
if (storage_size_str == NULL) {
|
||||||
if (size < 1024) {
|
static char size_str[16];
|
||||||
snprintf(size_str, sizeof(size_str), "%s%uB", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size);
|
uint16_t usize = 1 << ((version_hw->storage_size >> 1) + 1);
|
||||||
} else {
|
uint16_t lsize = 1 << (version_hw->storage_size >> 1);
|
||||||
snprintf(size_str, sizeof(size_str), "%s%uK", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size / 1024);
|
|
||||||
|
// 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];
|
char tag_info[128];
|
||||||
|
|
||||||
if ((sak & 0x44) == 0x40) {
|
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;
|
type |= MTISO18092;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((sak & 0x02) == 0x00) { // SAK b2=0
|
if ((sak & 0x02) == 0x00) { // SAK b2=0
|
||||||
|
|
||||||
if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1
|
if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1
|
||||||
|
|
||||||
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=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
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19
|
||||||
printTag("MIFARE Classic 2K");
|
printTag("MIFARE Classic 2K");
|
||||||
type |= MTCLASSIC;
|
type |= MTCLASSIC;
|
||||||
} else { // SAK b2=0 b4=1 b5=1 b1=0
|
} 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
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38
|
||||||
printTag("SmartMX with MIFARE Classic 4K");
|
printTag("SmartMX with MIFARE Classic 4K");
|
||||||
type |= MTCLASSIC;
|
type |= MTCLASSIC;
|
||||||
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0
|
} 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 (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
|
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);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
|
||||||
printTag(tag_info);
|
printTag(tag_info);
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion
|
||||||
|
|
||||||
if (ats_hist_len > 0) {
|
if (ats_hist_len > 0) {
|
||||||
|
|
||||||
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 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 (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
||||||
printTag("MIFARE Plus S 4K in SL1");
|
printTag("MIFARE Plus S 4K in SL1");
|
||||||
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
} 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
|
} else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18
|
||||||
|
|
||||||
if ((atqa & 0x0040) == 0x0040) {
|
if ((atqa & 0x0040) == 0x0040) {
|
||||||
printTag("MIFARE Classic 4K CL2");
|
printTag("MIFARE Classic 4K CL2");
|
||||||
} else {
|
} 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
|
} else { // SAK b2=0 b4=1 b5=0
|
||||||
|
|
||||||
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09
|
||||||
|
|
||||||
if ((atqa & 0x0040) == 0x0040) {
|
if ((atqa & 0x0040) == 0x0040) {
|
||||||
printTag("MIFARE Mini 0.3K CL2");
|
printTag("MIFARE Mini 0.3K CL2");
|
||||||
} else {
|
} else {
|
||||||
printTag("MIFARE Mini 0.3K");
|
printTag("MIFARE Mini 0.3K");
|
||||||
}
|
}
|
||||||
type |= MTMINI;
|
type |= MTMINI;
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=1 b5=0 b1=0
|
} 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
|
if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28
|
||||||
printTag("SmartMX with MIFARE Classic 1K");
|
printTag("SmartMX with MIFARE Classic 1K");
|
||||||
printTag("FM1208-10 with MIFARE Classic 1K");
|
printTag("FM1208-10 with MIFARE Classic 1K");
|
||||||
printTag("FM1216-137 with MIFARE Classic 1K");
|
printTag("FM1216-137 with MIFARE Classic 1K");
|
||||||
type |= MTCLASSIC;
|
type |= MTCLASSIC;
|
||||||
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0
|
} 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 (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
|
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);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str);
|
||||||
printTag(tag_info);
|
printTag(tag_info);
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion
|
} else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion
|
||||||
|
|
||||||
if (ats_hist_len > 0) {
|
if (ats_hist_len > 0) {
|
||||||
|
|
||||||
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 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 (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
||||||
printTag("MIFARE Plus S 2K in SL1");
|
printTag("MIFARE Plus S 2K in SL1");
|
||||||
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
} 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??");
|
printTag("Unrecognized MIFARE Plus??");
|
||||||
}
|
}
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) {
|
} 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) {
|
if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
|
||||||
printTag("MIFARE Plus SE 1K 17pF");
|
printTag("MIFARE Plus SE 1K 17pF");
|
||||||
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
|
} 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??");
|
printTag("Unrecognized MIFARE Plus SE??");
|
||||||
}
|
}
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if ((atqa & 0x0040) == 0x0040) {
|
if ((atqa & 0x0040) == 0x0040) {
|
||||||
printTag("MIFARE Classic 1K CL2 with ATS!");
|
printTag("MIFARE Classic 1K CL2 with ATS!");
|
||||||
} else {
|
} 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
|
} else { // SAK b2=0 b4=0
|
||||||
|
|
||||||
if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1
|
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
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11
|
||||||
printTag("MIFARE Plus 4K in SL2");
|
printTag("MIFARE Plus 4K in SL2");
|
||||||
type |= MTPLUS;
|
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");
|
printTag("MIFARE Plus 2K in SL2");
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=0 b5=0
|
} else { // SAK b2=0 b4=0 b5=0
|
||||||
|
|
||||||
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1
|
if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1
|
||||||
printTag("TNP3xxx (TagNPlay, Activision Game Appliance)");
|
printTag("TNP3xxx (TagNPlay, Activision Game Appliance)");
|
||||||
type |= MTCLASSIC;
|
type |= MTCLASSIC;
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=0 b5=0 b1=0
|
} 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 ((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 (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_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion
|
||||||
|
|
||||||
if ((version_hw->product_type & 0x7F) == 0x02) {
|
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);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL0/SL3", product_type_str, major_product_version_str, storage_size_str);
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else if (((version_hw->product_type & 0x7F) == 0x01) ||
|
} else if (((version_hw->product_type & 0x7F) == 0x01) ||
|
||||||
(version_hw->product_type == 0x08) ||
|
(version_hw->product_type == 0x08) ||
|
||||||
(version_hw->product_type == 0x91)) {
|
(version_hw->product_type == 0x91)) {
|
||||||
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
||||||
type |= MTDESFIRE;
|
type |= MTDESFIRE;
|
||||||
|
|
||||||
} else if (version_hw->product_type == 0x04) {
|
} 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);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
||||||
type |= (MTDESFIRE | MT424);
|
type |= (MTDESFIRE | MT424);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
||||||
}
|
}
|
||||||
printTag(tag_info);
|
printTag(tag_info);
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion
|
||||||
|
|
||||||
if (ats_hist_len > 0) {
|
if (ats_hist_len > 0) {
|
||||||
|
|
||||||
if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 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 (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) {
|
||||||
|
|
||||||
if ((atqa & 0xFF0F) == 0x0004) {
|
if ((atqa & 0xFF0F) == 0x0004) {
|
||||||
printTag("MIFARE Plus S 2K in SL0/SL3");
|
printTag("MIFARE Plus S 2K in SL0/SL3");
|
||||||
} else if ((atqa & 0xFF0F) == 0x0002) {
|
} 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 {
|
} else {
|
||||||
printTag("Unrecognized MIFARE Plus??");
|
printTag("Unrecognized MIFARE Plus??");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
} else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) {
|
||||||
printTag("MIFARE Plus X 2K/4K in SL0/SL3");
|
printTag("MIFARE Plus X 2K/4K in SL0/SL3");
|
||||||
|
|
||||||
} else if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
|
} else if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) {
|
||||||
printTag("MIFARE Plus SE 1K 17pF");
|
printTag("MIFARE Plus SE 1K 17pF");
|
||||||
|
|
||||||
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
|
} else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) {
|
||||||
printTag("MIFARE Plus SE 1K 70pF");
|
printTag("MIFARE Plus SE 1K 70pF");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
printTag("Unrecognized MIFARE Plus??");
|
printTag("Unknown MIFARE Plus");
|
||||||
}
|
}
|
||||||
type |= MTPLUS;
|
type |= MTPLUS;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if ((atqa == 0x0001) || (atqa == 0x0004)) {
|
if ((atqa == 0x0001) || (atqa == 0x0004)) {
|
||||||
printTag("HID SEOS (smartmx / javacard)");
|
printTag("HID SEOS (smartmx / javacard)");
|
||||||
type |= HID_SEOS;
|
type |= HID_SEOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atqa == 0x0004) {
|
if (atqa == 0x0004) {
|
||||||
printTag("EMV");
|
printTag("EMV");
|
||||||
type |= MTEMV;
|
type |= MTEMV;
|
||||||
|
@ -2128,11 +2223,15 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta
|
||||||
} else {
|
} else {
|
||||||
printTag("Unknown tag claims to support RATS in SAK but does not...");
|
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
|
} 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
|
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);
|
snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str);
|
||||||
printTag(tag_info);
|
printTag(tag_info);
|
||||||
|
|
||||||
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion
|
} else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion
|
||||||
|
|
||||||
int status = mfuc_test_authentication_support();
|
int status = mfuc_test_authentication_support();
|
||||||
if (status == PM3_SUCCESS) {
|
if (status == PM3_SUCCESS) {
|
||||||
// TODO: read page 2/3, then ??
|
// 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 {
|
} else {
|
||||||
printTag("MIFARE Ultralight");
|
printTag("MIFARE Ultralight");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
type |= MTULTRALIGHT;
|
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
|
} else { // SAK b2=1
|
||||||
|
|
||||||
if (sak == 0x0A) {
|
if (sak == 0x0A) {
|
||||||
|
|
||||||
if (atqa == 0x0003) {
|
if (atqa == 0x0003) {
|
||||||
// Uses Shanghai algo
|
// Uses Shanghai algo
|
||||||
printTag("FM11RF005SH (FUDAN Shanghai Metro)");
|
printTag("FM11RF005SH (FUDAN Shanghai Metro)");
|
||||||
type |= MTFUDAN;
|
type |= MTFUDAN;
|
||||||
|
|
||||||
} else if (atqa == 0x0005) {
|
} else if (atqa == 0x0005) {
|
||||||
printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)");
|
printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)");
|
||||||
type |= MTFUDAN;
|
type |= MTFUDAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (sak == 0x53) {
|
} else if (sak == 0x53) {
|
||||||
printTag("FM11RF08SH (FUDAN)");
|
printTag("FM11RF08SH (FUDAN)");
|
||||||
type |= MTFUDAN;
|
type |= MTFUDAN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == MTNONE) {
|
if (type == MTNONE) {
|
||||||
PrintAndLogEx(WARNING, " failed to fingerprint");
|
PrintAndLogEx(WARNING, " failed to fingerprint");
|
||||||
}
|
}
|
||||||
|
@ -2803,8 +2908,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
|
||||||
|
|
||||||
}
|
}
|
||||||
DropField();
|
DropField();
|
||||||
if (verbose == false && found)
|
if (verbose == false && found) {
|
||||||
PrintAndLogEx(INFO, "----------------------------------------------------");
|
PrintAndLogEx(INFO, "----------------------------------------------------");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2814,7 +2920,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
|
||||||
PrintAndLogEx(INFO, "proprietary iso18092 card found");
|
PrintAndLogEx(INFO, "proprietary iso18092 card found");
|
||||||
} else {
|
} 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) {
|
if ((card.sak & 0x20) == 0x20) {
|
||||||
PrintAndLogEx(INFO, "--> SAK incorrectly claims that card supports RATS <--");
|
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;
|
return PM3_EFAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintAndLogEx(INFO, "");
|
// PrintAndLogEx(INFO, "");
|
||||||
|
|
||||||
uint16_t isMagic = 0;
|
uint16_t isMagic = 0;
|
||||||
|
|
||||||
|
|
|
@ -1411,7 +1411,7 @@ static bool HF14B_ask_ct_reader(bool verbose) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HF14B_picopass_reader(bool verbose, bool info) {
|
static bool HF14B_picopass_reader(bool verbose) {
|
||||||
|
|
||||||
iso14b_raw_cmd_t packet = {
|
iso14b_raw_cmd_t packet = {
|
||||||
.flags = (ISO14B_CONNECT | ISO14B_SELECT_PICOPASS | ISO14B_DISCONNECT),
|
.flags = (ISO14B_CONNECT | ISO14B_SELECT_PICOPASS | ISO14B_DISCONNECT),
|
||||||
|
@ -1437,10 +1437,8 @@ bool HF14B_picopass_reader(bool verbose, bool info) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memcpy(card, resp.data.asBytes, sizeof(picopass_hdr_t));
|
memcpy(card, resp.data.asBytes, sizeof(picopass_hdr_t));
|
||||||
if (info) {
|
PrintAndLogEx(NORMAL, "");
|
||||||
PrintAndLogEx(NORMAL, "");
|
PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
|
||||||
PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
|
|
||||||
}
|
|
||||||
free(card);
|
free(card);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3038,7 +3036,6 @@ int infoHF14B(bool verbose, bool do_aid_search) {
|
||||||
// get and print general info about all known 14b chips
|
// get and print general info about all known 14b chips
|
||||||
int readHF14B(bool loop, bool verbose, bool read_plot) {
|
int readHF14B(bool loop, bool verbose, bool read_plot) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool info = true;
|
|
||||||
int res = PM3_SUCCESS;
|
int res = PM3_SUCCESS;
|
||||||
do {
|
do {
|
||||||
found = false;
|
found = false;
|
||||||
|
@ -3054,7 +3051,7 @@ int readHF14B(bool loop, bool verbose, bool read_plot) {
|
||||||
goto plot;
|
goto plot;
|
||||||
|
|
||||||
// Picopass
|
// Picopass
|
||||||
found |= HF14B_picopass_reader(verbose, info);
|
found |= HF14B_picopass_reader(verbose);
|
||||||
if (found)
|
if (found)
|
||||||
goto plot;
|
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 infoHF14B(bool verbose, bool do_aid_search);
|
||||||
int readHF14B(bool loop, bool verbose, bool read_plot);
|
int readHF14B(bool loop, bool verbose, bool read_plot);
|
||||||
bool HF14B_picopass_reader(bool verbose, bool info);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -40,19 +40,21 @@
|
||||||
#include "crypto/asn1utils.h" // ASN1 decoder
|
#include "crypto/asn1utils.h" // ASN1 decoder
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "generator.h"
|
#include "generator.h"
|
||||||
#include "cmdhf14b.h"
|
|
||||||
#include "cmdhw.h"
|
#include "cmdhw.h"
|
||||||
#include "hidsio.h"
|
#include "hidsio.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define ICLASS_DEBIT_KEYTYPE ( 0x88 )
|
||||||
|
#define ICLASS_CREDIT_KEYTYPE ( 0x18 )
|
||||||
|
|
||||||
#define NUM_CSNS 9
|
#define NUM_CSNS 9
|
||||||
#define MAC_ITEM_SIZE 24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes
|
#define MAC_ITEM_SIZE 24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes
|
||||||
#define ICLASS_KEYS_MAX 8
|
#define ICLASS_KEYS_MAX 8
|
||||||
#define ICLASS_AUTH_RETRY 10
|
#define ICLASS_AUTH_RETRY 10
|
||||||
#define ICLASS_CFG_BLK_SR_BIT 0xA0 // indicates SIO present when set in block6[0] (legacy tags)
|
#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_DECRYPTION_BIN "iclass_decryptionkey.bin"
|
||||||
#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic"
|
#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic"
|
||||||
#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic"
|
#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic"
|
||||||
|
|
||||||
static void print_picopass_info(const picopass_hdr_t *hdr);
|
static void print_picopass_info(const picopass_hdr_t *hdr);
|
||||||
void print_picopass_header(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);
|
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)) {
|
if (arg_get_lit(ctx, 4)) {
|
||||||
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
||||||
keyType = 0x18; //credit key
|
keyType = ICLASS_CREDIT_KEYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool elite = arg_get_lit(ctx, 5);
|
bool elite = arg_get_lit(ctx, 5);
|
||||||
|
@ -2958,7 +2960,8 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
CLIParserInit(&ctx, "hf iclass tear",
|
CLIParserInit(&ctx, "hf iclass tear",
|
||||||
"Tear off an iCLASS tag block\n"
|
"Tear off an iCLASS tag block\n"
|
||||||
"e-purse usually 300-500us to trigger the erase phase\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 -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 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600\n"
|
||||||
"hf iclass tear --blk 2 -d fdffffffffffffff --ki 1 --credit -s 400 -e 500"
|
"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("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, "loop", "<dec>", "number of times to loop per tearoff time"),
|
||||||
arg_int0(NULL, "sleep", "<ms>", "Sleep between each tear"),
|
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
|
arg_param_end
|
||||||
};
|
};
|
||||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
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_end = arg_get_int_def(ctx, 14, tearoff_start + tearoff_increment + 500);
|
||||||
int tearoff_loop = arg_get_int_def(ctx, 15, 1);
|
int tearoff_loop = arg_get_int_def(ctx, 15, 1);
|
||||||
int tearoff_sleep = arg_get_int_def(ctx, 16, 0);
|
int tearoff_sleep = arg_get_int_def(ctx, 16, 0);
|
||||||
|
bool run_on_device = arg_get_lit(ctx, 17);
|
||||||
|
|
||||||
CLIParserFree(ctx);
|
CLIParserFree(ctx);
|
||||||
|
|
||||||
|
@ -3078,13 +3083,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int loop_count = 0;
|
int loop_count = 0;
|
||||||
int isok = 0;
|
int isok = PM3_SUCCESS;
|
||||||
bool read_ok = false;
|
bool read_ok = false;
|
||||||
uint8_t keyType = 0x88; // debit key
|
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
|
||||||
|
|
||||||
if (use_credit_key) {
|
if (use_credit_key) {
|
||||||
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
||||||
keyType = 0x18; // credit key
|
keyType = ICLASS_CREDIT_KEYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth == false) {
|
if (auth == false) {
|
||||||
|
@ -3130,6 +3134,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
return PM3_ESOFT;
|
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;
|
picopass_hdr_t *hdr = &r->header.hdr;
|
||||||
uint8_t pagemap = get_pagemap(hdr);
|
uint8_t pagemap = get_pagemap(hdr);
|
||||||
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||||
|
@ -3138,13 +3148,13 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pagemap == 0x0) {
|
if (pagemap == 0x0) {
|
||||||
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled"));
|
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled"));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_auth = auth;
|
|
||||||
|
|
||||||
// perform initial read here, repeat if failed or 00s
|
// perform initial read here, repeat if failed or 00s
|
||||||
|
bool read_auth = auth;
|
||||||
uint8_t data_read_orig[8] = {0};
|
uint8_t data_read_orig[8] = {0};
|
||||||
uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||||
bool first_read = false;
|
bool first_read = false;
|
||||||
|
@ -3180,47 +3190,58 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
// clear trace log
|
// clear trace log
|
||||||
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
|
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
|
||||||
|
|
||||||
PrintAndLogEx(INFO, "---------------------------------------");
|
if (run_on_device) {
|
||||||
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(NORMAL, "");
|
||||||
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
PrintAndLogEx(INFO, "---------------------------------------");
|
||||||
isok = PM3_EOPABORTED;
|
PrintAndLogEx(NORMAL, "");
|
||||||
goto out;
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");
|
||||||
}
|
PrintAndLogEx(NORMAL, "");
|
||||||
|
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
||||||
|
|
||||||
// set tear off trigger
|
iclass_tearblock_req_t payload = {
|
||||||
clearCommandBuffer();
|
.req.use_raw = rawkey,
|
||||||
tearoff_params_t params = {
|
.req.use_elite = elite,
|
||||||
.delay_us = (tearoff_start & 0xFFFF),
|
.req.use_credit_key = use_credit_key,
|
||||||
.on = true,
|
.req.use_replay = use_replay,
|
||||||
.off = false
|
.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);
|
clearCommandBuffer();
|
||||||
if (res != PM3_SUCCESS) {
|
SendCommandNG(CMD_HF_ICLASS_TEARBL, (uint8_t *)&payload, sizeof(payload));
|
||||||
PrintAndLogEx(WARNING, "Failed to configure tear off");
|
|
||||||
isok = PM3_ESOFT;
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_TEARBL, &resp, 1000)) {
|
||||||
goto out;
|
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.
|
} else {
|
||||||
iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
|
|
||||||
|
|
||||||
// read the data back
|
PrintAndLogEx(INFO, "---------------------------------------");
|
||||||
uint8_t data_read[8] = {0};
|
PrintAndLogEx(NORMAL, "");
|
||||||
first_read = false;
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
||||||
reread = false;
|
PrintAndLogEx(NORMAL, "");
|
||||||
bool decrease = false;
|
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
||||||
|
// Main loop
|
||||||
while (first_read == false) {
|
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
|
||||||
|
|
||||||
if (kbd_enter_pressed()) {
|
if (kbd_enter_pressed()) {
|
||||||
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
||||||
|
@ -3228,100 +3249,205 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockno == 1) {
|
// set tear off trigger
|
||||||
read_auth = false;
|
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 (tearoff_loop > 1) {
|
||||||
if (res == PM3_SUCCESS && !reread) {
|
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" iter", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1);
|
||||||
if (memcmp(data_read, zeros, 8) == 0) {
|
} else {
|
||||||
reread = true;
|
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF));
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// 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;
|
first_read = true;
|
||||||
reread = false;
|
reread = false;
|
||||||
|
} else if (res != PM3_SUCCESS) {
|
||||||
|
decrease = true;
|
||||||
}
|
}
|
||||||
} else if (res == PM3_SUCCESS && reread) {
|
|
||||||
first_read = true;
|
readcount++;
|
||||||
reread = false;
|
|
||||||
} else if (res != PM3_SUCCESS) {
|
|
||||||
decrease = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// if there was an error reading repeat the tearoff with the same delay
|
if (readcount > 1) {
|
||||||
if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) {
|
PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount);
|
||||||
tearoff_start -= tearoff_increment;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool tear_success = true;
|
// if there was an error reading repeat the tearoff with the same delay
|
||||||
bool expected_values = true;
|
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) {
|
bool tear_success = true;
|
||||||
tear_success = false;
|
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) {
|
expected_values = false;
|
||||||
erase_phase = true;
|
|
||||||
PrintAndLogEx(NORMAL, "");
|
|
||||||
PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES"));
|
|
||||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (erase_phase) {
|
if (memcmp(data_read, ff_data, 8) == 0 &&
|
||||||
PrintAndLogEx(NORMAL, "");
|
memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||||
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
|
|
||||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
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 {
|
} else {
|
||||||
PrintAndLogEx(NORMAL, "");
|
|
||||||
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase"));
|
|
||||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockno == 1) {
|
if (erase_phase) {
|
||||||
if (data_read[0] != data_read_orig[0]) {
|
PrintAndLogEx(NORMAL, "");
|
||||||
PrintAndLogEx(NORMAL, "");
|
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
|
||||||
PrintAndLogEx(SUCCESS, "Application limit changed, from %u to %u", data_read_orig[0], data_read[0]);
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||||
isok = PM3_SUCCESS;
|
} else {
|
||||||
goto out;
|
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]) {
|
bool goto_out = false;
|
||||||
PrintAndLogEx(NORMAL, "");
|
if (blockno == 2) {
|
||||||
PrintAndLogEx(SUCCESS, "Fuse changed, from %02x to %02x", data_read_orig[7], data_read[7]);
|
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||||
isok = PM3_SUCCESS;
|
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;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (tear_success) { // tearoff succeeded with expected values
|
if (tear_success) { // tearoff succeeded with expected values
|
||||||
|
|
||||||
read_ok = true;
|
read_ok = true;
|
||||||
tear_success = true;
|
tear_success = true;
|
||||||
|
|
||||||
PrintAndLogEx(NORMAL, "");
|
PrintAndLogEx(NORMAL, "");
|
||||||
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
|
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
|
||||||
, sprint_hex_inrow(data_read, sizeof(data_read)),
|
, sprint_hex_inrow(data_read, sizeof(data_read)),
|
||||||
(expected_values) ? _GREEN_(" -> Expected values!") : ""
|
(expected_values) ? _GREEN_(" -> Expected values!") : ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_count++;
|
loop_count++;
|
||||||
|
|
||||||
if (loop_count == tearoff_loop) {
|
if (loop_count == tearoff_loop) {
|
||||||
tearoff_start += tearoff_increment;
|
tearoff_start += tearoff_increment;
|
||||||
loop_count = 0;
|
loop_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tearoff_sleep) {
|
if (tearoff_sleep) {
|
||||||
msleep(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];
|
xor_div_key[i] = old_div_key[i] ^ new_div_key[i];
|
||||||
}
|
}
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
PrintAndLogEx(SUCCESS, "Old div key......... %s", sprint_hex(old_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, "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, "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);
|
WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp);
|
||||||
|
|
||||||
if (resp.status == PM3_SUCCESS) {
|
if (resp.status == PM3_SUCCESS) {
|
||||||
|
PrintAndLogEx(NORMAL, "");
|
||||||
PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!"));
|
PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!"));
|
||||||
repeat = false;
|
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) {
|
} else if (resp.status == PM3_ESOFT) {
|
||||||
|
PrintAndLogEx(NORMAL, "");
|
||||||
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _RED_("failed/errors"));
|
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _RED_("failed/errors"));
|
||||||
repeat = false;
|
repeat = false;
|
||||||
} else if (resp.status == PM3_EINVARG) {
|
} 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
|
//Standalone Command Start
|
||||||
CLIParserContext *ctx;
|
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, "Original Key: " _GREEN_("%s"), sprint_hex(original_key, sizeof(original_key)));
|
||||||
PrintAndLogEx(SUCCESS, "Weak Key: " _GREEN_("%s"), sprint_hex(key, sizeof(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, "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++;
|
index++;
|
||||||
|
@ -4761,8 +4893,14 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) {
|
||||||
return PM3_EINVARG;
|
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);
|
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(WARNING, _YELLOW_("If the process completed successfully, you can now run 'hf iclass legbrute' with the partial key found."));
|
||||||
|
|
||||||
PrintAndLogEx(NORMAL, "");
|
PrintAndLogEx(NORMAL, "");
|
||||||
|
@ -5773,7 +5911,7 @@ static command_t CommandTable[] = {
|
||||||
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
|
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
|
||||||
{"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"},
|
{"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"},
|
{"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"},
|
{"unhash", CmdHFiClassUnhash, AlwaysAvailable, "Reverses a diversified key to retrieve hash0 pre-images after DES encryption"},
|
||||||
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
|
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
|
||||||
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
|
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
|
||||||
|
@ -5927,33 +6065,46 @@ int info_iclass(bool shallow_mod) {
|
||||||
uint8_t cardtype = get_mem_config(hdr);
|
uint8_t cardtype = get_mem_config(hdr);
|
||||||
PrintAndLogEx(SUCCESS, " Card type.... " _GREEN_("%s"), card_types[cardtype]);
|
PrintAndLogEx(SUCCESS, " Card type.... " _GREEN_("%s"), card_types[cardtype]);
|
||||||
|
|
||||||
if (HF14B_picopass_reader(false, false)) {
|
if (memcmp(hdr->csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
|
||||||
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)"));
|
|
||||||
} else {
|
|
||||||
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("NEW Silicon (No 14b support)"));
|
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("NEW Silicon (No 14b support)"));
|
||||||
|
} else {
|
||||||
|
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)"));
|
||||||
}
|
}
|
||||||
if (legacy) {
|
if (legacy) {
|
||||||
|
|
||||||
int res = PM3_ESOFT;
|
int res = PM3_ESOFT;
|
||||||
uint8_t key_type = 0x88; // debit key
|
|
||||||
|
|
||||||
uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0};
|
uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0};
|
||||||
// we take all raw bytes from response
|
// we take all raw bytes from response
|
||||||
memcpy(dump, p_response, sizeof(picopass_hdr_t));
|
memcpy(dump, p_response, sizeof(picopass_hdr_t));
|
||||||
|
|
||||||
|
bool found_aa1 = false;
|
||||||
|
bool found_aa2 = false;
|
||||||
uint8_t key[8] = {0};
|
uint8_t key[8] = {0};
|
||||||
for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) {
|
for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) {
|
||||||
|
|
||||||
memcpy(key, iClass_Key_Table[i], sizeof(key));
|
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) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res == PM3_SUCCESS) {
|
if (found_aa1) {
|
||||||
res = iclass_read_block_ex(key, 7, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false);
|
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) {
|
if (res == PM3_SUCCESS) {
|
||||||
|
|
||||||
BLOCK79ENCRYPTION aa1_encryption = (dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);
|
BLOCK79ENCRYPTION aa1_encryption = (dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);
|
||||||
|
|
|
@ -48,6 +48,301 @@
|
||||||
#include "mifare/mifarehost.h"
|
#include "mifare/mifarehost.h"
|
||||||
#include "crypto/originality.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);
|
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) {
|
&& card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x45", 4) == 0) {
|
||||||
PrintAndLogEx(SUCCESS, "NXP MF1ICS5004");
|
PrintAndLogEx(SUCCESS, "NXP MF1ICS5004");
|
||||||
} else if (fKeyType == MF_KEY_BD) {
|
} 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
|
} else
|
||||||
// other cards
|
// other cards
|
||||||
if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x46", 4) == 0) {
|
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) {
|
} else if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\xc0", 4) == 0) {
|
||||||
PrintAndLogEx(SUCCESS, "NXP MF1ICS5035");
|
PrintAndLogEx(SUCCESS, "NXP MF1ICS5035");
|
||||||
} else {
|
} 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");
|
PrintAndLogEx(SUCCESS, "dormakaba Saflok detected");
|
||||||
|
ParseAndPrintSaflokData(&e_sector[0], &e_sector[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -10229,6 +10526,64 @@ static int CmdHF14AMfISEN(const char *Cmd) {
|
||||||
return PM3_SUCCESS;
|
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[] = {
|
static command_t CommandTable[] = {
|
||||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||||
{"list", CmdHF14AMfList, AlwaysAvailable, "List MIFARE history"},
|
{"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"},
|
{"fchk", CmdHF14AMfChk_fast, IfPm3Iso14443a, "Check keys fast, targets all keys on card"},
|
||||||
{"decrypt", CmdHf14AMfDecryptBytes, AlwaysAvailable, "Decrypt Crypto1 data from sniff or trace"},
|
{"decrypt", CmdHf14AMfDecryptBytes, AlwaysAvailable, "Decrypt Crypto1 data from sniff or trace"},
|
||||||
{"supercard", CmdHf14AMfSuperCard, IfPm3Iso14443a, "Extract info from a `super card`"},
|
{"supercard", CmdHf14AMfSuperCard, IfPm3Iso14443a, "Extract info from a `super card`"},
|
||||||
|
{"bambukeys", CmdHF14AMfBambuKeys, AlwaysAvailable, "Generate key table for Bambu Lab filament tag"},
|
||||||
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"},
|
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"},
|
||||||
{"auth4", CmdHF14AMfAuth4, IfPm3Iso14443a, "ISO14443-4 AES authentication"},
|
{"auth4", CmdHF14AMfAuth4, IfPm3Iso14443a, "ISO14443-4 AES authentication"},
|
||||||
{"acl", CmdHF14AMfAcl, AlwaysAvailable, "Decode and print MIFARE Classic access rights bytes"},
|
{"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?
|
// is LSB set?
|
||||||
if (fsize & 1)
|
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
|
else
|
||||||
snprintf(buf, sizeof(buf), "%02X, (%u bytes)", fsize, lsize);
|
snprintf(buf, sizeof(buf), "%02X, (%u bytes)", fsize, lsize);
|
||||||
return buf;
|
return buf;
|
||||||
|
@ -3925,17 +3925,17 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) {
|
||||||
CLIParserContext *ctx;
|
CLIParserContext *ctx;
|
||||||
CLIParserInit(&ctx, "hf mfu aesauth",
|
CLIParserInit(&ctx, "hf mfu aesauth",
|
||||||
"Tests AES key on Mifare Ultralight AES tags.\n"
|
"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 0: DataProtKey (default)\n"
|
||||||
"Key index 1: UIDRetrKey\n"
|
"Key index 1: UIDRetrKey\n"
|
||||||
"Key index 2: OriginalityKey\n",
|
"Key index 2: OriginalityKey\n",
|
||||||
"hf mfu aesauth\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[] = {
|
void *argtable[] = {
|
||||||
arg_param_begin,
|
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_int0("i", "index", "<0..2>", "Key index, default: 0"),
|
||||||
arg_lit0("k", NULL, "Keep field on (only if a key is provided)"),
|
arg_lit0("k", NULL, "Keep field on (only if a key is provided)"),
|
||||||
arg_param_end
|
arg_param_end
|
||||||
|
|
|
@ -356,6 +356,7 @@ const static vocabulary_t vocabulary[] = {
|
||||||
{ 0, "hf mf fchk" },
|
{ 0, "hf mf fchk" },
|
||||||
{ 1, "hf mf decrypt" },
|
{ 1, "hf mf decrypt" },
|
||||||
{ 0, "hf mf supercard" },
|
{ 0, "hf mf supercard" },
|
||||||
|
{ 1, "hf mf bambukeys" },
|
||||||
{ 0, "hf mf auth4" },
|
{ 0, "hf mf auth4" },
|
||||||
{ 1, "hf mf acl" },
|
{ 1, "hf mf acl" },
|
||||||
{ 0, "hf mf dump" },
|
{ 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
|
* 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_reverse(char *buf, size_t len);
|
||||||
void str_inverse_hex(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_inverse_bin(char *buf, size_t len);
|
||||||
|
void str_trim(char *s);
|
||||||
|
|
||||||
char *str_dup(const char *src);
|
char *str_dup(const char *src);
|
||||||
char *str_ndup(const char *src, size_t len);
|
char *str_ndup(const char *src, size_t len);
|
||||||
size_t str_nlen(const char *src, size_t maxlen);
|
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 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 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);
|
int binarray_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const uint8_t *arr, int arrlen);
|
||||||
|
|
|
@ -3718,7 +3718,7 @@
|
||||||
},
|
},
|
||||||
"hf iclass tear": {
|
"hf iclass tear": {
|
||||||
"command": "hf iclass tear",
|
"command": "hf iclass tear",
|
||||||
"description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards",
|
"description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`",
|
||||||
"notes": [
|
"notes": [
|
||||||
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600",
|
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600",
|
||||||
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600",
|
"hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600",
|
||||||
|
@ -3742,9 +3742,10 @@
|
||||||
"-i <dec> tearoff delay increment (in us) - default 10",
|
"-i <dec> tearoff delay increment (in us) - default 10",
|
||||||
"-e <dec> tearoff delay end (in us) must be a higher value than the start delay",
|
"-e <dec> tearoff delay end (in us) must be a higher value than the start delay",
|
||||||
"--loop <dec> number of times to loop per tearoff time",
|
"--loop <dec> number of times to loop per tearoff time",
|
||||||
"--sleep <ms> Sleep between each tear"
|
"--sleep <ms> Sleep between each tear",
|
||||||
|
"--arm Runs the commands on device side and tries to stabilize tears"
|
||||||
],
|
],
|
||||||
"usage": "hf iclass tear [-hv] [-k <hex>] [--ki <dec>] --blk <dec> -d <hex> [-m <hex>] [--credit] [--elite] [--raw] [--nr] [--shallow] -s <dec> [-i <dec>] [-e <dec>] [--loop <dec>] [--sleep <ms>]"
|
"usage": "hf iclass tear [-hv] [-k <hex>] [--ki <dec>] --blk <dec> -d <hex> [-m <hex>] [--credit] [--elite] [--raw] [--nr] [--shallow] -s <dec> [-i <dec>] [-e <dec>] [--loop <dec>] [--sleep <ms>] [--arm]"
|
||||||
},
|
},
|
||||||
"hf iclass unhash": {
|
"hf iclass unhash": {
|
||||||
"command": "hf iclass unhash",
|
"command": "hf iclass unhash",
|
||||||
|
@ -4411,6 +4412,23 @@
|
||||||
],
|
],
|
||||||
"usage": "hf mf autopwn [-hablv] [-k <hex>]... [-s <dec>] [-f <fn>] [--suffix <txt>] [--slow] [--mem] [--ns] [--mini] [--1k] [--2k] [--4k] [--in] [--im] [--is] [--ia] [--i2] [--i5]"
|
"usage": "hf mf autopwn [-hablv] [-k <hex>]... [-s <dec>] [-f <fn>] [--suffix <txt>] [--slow] [--mem] [--ns] [--mini] [--1k] [--2k] [--4k] [--in] [--im] [--is] [--ia] [--i2] [--i5]"
|
||||||
},
|
},
|
||||||
|
"hf mf bambukeys": {
|
||||||
|
"command": "hf mf bambukeys",
|
||||||
|
"description": "Generate keys for a Bambu Lab filament tag",
|
||||||
|
"notes": [
|
||||||
|
"hf mf bambukeys -r",
|
||||||
|
"hf mf bambukeys -r -d",
|
||||||
|
"hf mf bambukeys -u 11223344"
|
||||||
|
],
|
||||||
|
"offline": true,
|
||||||
|
"options": [
|
||||||
|
"-h, --help This help",
|
||||||
|
"-u, --uid <hex> UID (4 hex bytes)",
|
||||||
|
"-r Read UID from tag",
|
||||||
|
"-d Dump keys to file"
|
||||||
|
],
|
||||||
|
"usage": "hf mf bambukeys [-hrd] [-u <hex>]"
|
||||||
|
},
|
||||||
"hf mf brute": {
|
"hf mf brute": {
|
||||||
"command": "hf mf brute",
|
"command": "hf mf brute",
|
||||||
"description": "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.",
|
"description": "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.",
|
||||||
|
@ -5145,7 +5163,7 @@
|
||||||
},
|
},
|
||||||
"hf mf help": {
|
"hf mf help": {
|
||||||
"command": "hf mf help",
|
"command": "hf mf help",
|
||||||
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
|
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace bambukeys Generate key table for Bambu Lab filament tag acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
|
||||||
"notes": [
|
"notes": [
|
||||||
"hf mf list --frame -> show frame delay times",
|
"hf mf list --frame -> show frame delay times",
|
||||||
"hf mf list -1 -> use trace buffer"
|
"hf mf list -1 -> use trace buffer"
|
||||||
|
@ -7061,15 +7079,15 @@
|
||||||
},
|
},
|
||||||
"hf mfu aesauth": {
|
"hf mfu aesauth": {
|
||||||
"command": "hf mfu aesauth",
|
"command": "hf mfu aesauth",
|
||||||
"description": "Tests AES key on Mifare Ultralight AES tags. If key is not specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey",
|
"description": "Tests AES key on Mifare Ultralight AES tags. If no key is specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey",
|
||||||
"notes": [
|
"notes": [
|
||||||
"hf mfu aesauth",
|
"hf mfu aesauth",
|
||||||
"hf mfu aesauth --key <32 hex chars> --index <0..2>"
|
"hf mfu aesauth --key <16 hex bytes> --index <0..2>"
|
||||||
],
|
],
|
||||||
"offline": false,
|
"offline": false,
|
||||||
"options": [
|
"options": [
|
||||||
"-h, --help This help",
|
"-h, --help This help",
|
||||||
"--key <hex> Authentication key (16 bytes in hex)",
|
"--key <hex> AES key (16 hex bytes)",
|
||||||
"-i, --index <0..2> Key index, default: 0",
|
"-i, --index <0..2> Key index, default: 0",
|
||||||
"-k Keep field on (only if a key is provided)"
|
"-k Keep field on (only if a key is provided)"
|
||||||
],
|
],
|
||||||
|
@ -13352,8 +13370,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"commands_extracted": 767,
|
"commands_extracted": 768,
|
||||||
"extracted_by": "PM3Help2JSON v1.00",
|
"extracted_by": "PM3Help2JSON v1.00",
|
||||||
"extracted_on": "2025-05-25T12:38:36"
|
"extracted_on": "2025-05-29T23:30:20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,6 +527,7 @@ Check column "offline" for their availability.
|
||||||
|`hf mf fchk `|N |`Check keys fast, targets all keys on card`
|
|`hf mf fchk `|N |`Check keys fast, targets all keys on card`
|
||||||
|`hf mf decrypt `|Y |`Decrypt Crypto1 data from sniff or trace`
|
|`hf mf decrypt `|Y |`Decrypt Crypto1 data from sniff or trace`
|
||||||
|`hf mf supercard `|N |`Extract info from a `super card``
|
|`hf mf supercard `|N |`Extract info from a `super card``
|
||||||
|
|`hf mf bambukeys `|Y |`Generate key table for Bambu Lab filament tag`
|
||||||
|`hf mf auth4 `|N |`ISO14443-4 AES authentication`
|
|`hf mf auth4 `|N |`ISO14443-4 AES authentication`
|
||||||
|`hf mf acl `|Y |`Decode and print MIFARE Classic access rights bytes`
|
|`hf mf acl `|Y |`Decode and print MIFARE Classic access rights bytes`
|
||||||
|`hf mf dump `|N |`Dump MIFARE Classic tag to binary file`
|
|`hf mf dump `|N |`Dump MIFARE Classic tag to binary file`
|
||||||
|
|
|
@ -87,6 +87,17 @@ typedef struct {
|
||||||
uint8_t mac[4];
|
uint8_t mac[4];
|
||||||
} PACKED iclass_writeblock_req_t;
|
} PACKED iclass_writeblock_req_t;
|
||||||
|
|
||||||
|
// iCLASS tearoff block request data structure
|
||||||
|
typedef struct {
|
||||||
|
iclass_auth_req_t req;
|
||||||
|
uint8_t data[8];
|
||||||
|
uint8_t mac[4];
|
||||||
|
int tear_start;
|
||||||
|
int tear_end;
|
||||||
|
int increment;
|
||||||
|
int tear_loop;
|
||||||
|
} PACKED iclass_tearblock_req_t;
|
||||||
|
|
||||||
// iCLASS write block request data structure
|
// iCLASS write block request data structure
|
||||||
typedef struct {
|
typedef struct {
|
||||||
iclass_auth_req_t req;
|
iclass_auth_req_t req;
|
||||||
|
|
|
@ -668,6 +668,7 @@ typedef struct {
|
||||||
#define CMD_HF_ICLASS_RESTORE 0x039B
|
#define CMD_HF_ICLASS_RESTORE 0x039B
|
||||||
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
|
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
|
||||||
#define CMD_HF_ICLASS_RECOVER 0x039D
|
#define CMD_HF_ICLASS_RECOVER 0x039D
|
||||||
|
#define CMD_HF_ICLASS_TEARBL 0x039E
|
||||||
|
|
||||||
|
|
||||||
// For ISO1092 / FeliCa
|
// For ISO1092 / FeliCa
|
||||||
|
|
|
@ -272,7 +272,9 @@ static uint64_t **unpredictable_nested(NtpKs1List *pNKL, uint32_t keyCounts[]) {
|
||||||
pthread_cond_wait(&status_cond, &status_mutex);
|
pthread_cond_wait(&status_cond, &status_mutex);
|
||||||
activeThreads = 0;
|
activeThreads = 0;
|
||||||
for (int i = 0; i < NUM_THREADS; i++) {
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
if (thread_status[i]) activeThreads++;
|
if (thread_status[i]) {
|
||||||
|
activeThreads++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ endif
|
||||||
|
|
||||||
# OS X needs linking to openssl
|
# OS X needs linking to openssl
|
||||||
ifeq ($(USE_BREW),1)
|
ifeq ($(USE_BREW),1)
|
||||||
MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@1.1/include
|
MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@3.5/include
|
||||||
MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@1.1/lib
|
MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@3.5/lib
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(USE_MACPORTS),1)
|
ifeq ($(USE_MACPORTS),1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue