mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-07-05 20:41:34 -07:00
the device side of iclass tear off is implemented. The base was done by @antiklesys. This version differs by the concept of trying to stabilize weak bits by performing a write operation in conjuction with the detected tear. Its untested but I can replicate most of the tears we performed client side. You will need to call the proxmark3 client with -f
, ./pm3 -f
to force flush out text which is needed for the inplace printing. I thought this was done automatically but it wasnt. hf iclass tear --arm + all the normal params
to run on device side
This commit is contained in:
parent
f0022e4280
commit
804acfbefa
8 changed files with 674 additions and 192 deletions
|
@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
|||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Modified `hf iclass tear` - now has a device side implementation also. @antiklesys (@iceman1001)
|
||||
- Changed `hf iclass info` - now uses CSN values based checks (@antiklesys)
|
||||
- Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001)
|
||||
- Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001)
|
||||
|
|
|
@ -2230,6 +2230,10 @@ static void PacketReceived(PacketCommandNG *packet) {
|
|||
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
|
||||
break;
|
||||
}
|
||||
case CMD_HF_ICLASS_TEARBL: {
|
||||
iClass_TearBlock((iclass_tearblock_req_t *)packet->data.asBytes);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_HFSNIFF
|
||||
|
|
428
armsrc/iclass.c
428
armsrc/iclass.c
|
@ -38,6 +38,7 @@
|
|||
#include "iso15693.h"
|
||||
#include "iclass_cmd.h" // iclass_card_select_t struct
|
||||
#include "i2c.h" // i2c defines (SIM module access)
|
||||
#include "printf.h"
|
||||
|
||||
uint8_t get_pagemap(const picopass_hdr_t *hdr) {
|
||||
return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
|
||||
|
@ -1244,7 +1245,6 @@ static bool iclass_send_cmd_with_retries(uint8_t *cmd, size_t cmdsize, uint8_t *
|
|||
while (tries-- > 0) {
|
||||
|
||||
iclass_send_as_reader(cmd, cmdsize, start_time, eof_time, shallow_mod);
|
||||
|
||||
if (resp == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1582,8 +1582,9 @@ bool iclass_read_block(uint16_t blockno, uint8_t *data, uint32_t *start_time, ui
|
|||
uint8_t c[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
|
||||
AddCrc(c + 1, 1);
|
||||
bool isOK = iclass_send_cmd_with_retries(c, sizeof(c), resp, sizeof(resp), 10, 2, start_time, ICLASS_READER_TIMEOUT_OTHERS, eof_time, shallow_mod);
|
||||
if (isOK)
|
||||
if (isOK) {
|
||||
memcpy(data, resp, 8);
|
||||
}
|
||||
return isOK;
|
||||
}
|
||||
|
||||
|
@ -1780,13 +1781,13 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac,
|
|||
}
|
||||
} else if (blockno == 3 || blockno == 4) {
|
||||
// check response. Key updates always return 0xffffffffffffffff
|
||||
uint8_t all_ff[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
if (memcmp(all_ff, resp, 8)) {
|
||||
uint8_t all_ff[PICOPASS_BLOCK_SIZE] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
if (memcmp(all_ff, resp, PICOPASS_BLOCK_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// check response. All other updates return unchanged data
|
||||
if (memcmp(data, resp, 8)) {
|
||||
if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1829,7 +1830,7 @@ void iClass_WriteBlock(uint8_t *msg) {
|
|||
}
|
||||
|
||||
// new block data
|
||||
memcpy(write + 2, payload->data, 8);
|
||||
memcpy(write + 2, payload->data, PICOPASS_BLOCK_SIZE);
|
||||
|
||||
uint8_t pagemap = get_pagemap(&hdr);
|
||||
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||
|
@ -1847,7 +1848,7 @@ void iClass_WriteBlock(uint8_t *msg) {
|
|||
// Secure tags uses MAC
|
||||
uint8_t wb[9];
|
||||
wb[0] = payload->req.blockno;
|
||||
memcpy(wb + 1, payload->data, 8);
|
||||
memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE);
|
||||
|
||||
if (payload->req.use_credit_key)
|
||||
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
||||
|
@ -2074,6 +2075,417 @@ out:
|
|||
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
static void iclass_cmp_print(uint8_t *b1, uint8_t *b2, const char *header1, const char *header2) {
|
||||
|
||||
char line1[240] = {0};
|
||||
char line2[240] = {0};
|
||||
|
||||
strcat(line1, header1);
|
||||
strcat(line2, header2);
|
||||
|
||||
for (uint8_t i = 0; i < PICOPASS_BLOCK_SIZE; i++) {
|
||||
|
||||
int l1 = strlen(line1);
|
||||
int l2 = strlen(line2);
|
||||
|
||||
uint8_t hi1 = NIBBLE_HIGH(b1[i]);
|
||||
uint8_t low1 = NIBBLE_LOW(b1[i]);
|
||||
|
||||
uint8_t hi2 = NIBBLE_HIGH(b2[i]);
|
||||
uint8_t low2 = NIBBLE_LOW(b2[i]);
|
||||
|
||||
if (hi1 != hi2) {
|
||||
sprintf(line1 + l1, _RED_("%1X"), hi1);
|
||||
sprintf(line2 + l2, _GREEN_("%1X"), hi2);
|
||||
} else {
|
||||
sprintf(line1 + l1, "%1X", hi1);
|
||||
sprintf(line2 + l2, "%1X", hi2);
|
||||
}
|
||||
|
||||
l1 = strlen(line1);
|
||||
l2 = strlen(line2);
|
||||
|
||||
if (low1 != low2) {
|
||||
sprintf(line1 + l1, _RED_("%1X"), low1);
|
||||
sprintf(line2 + l2, _GREEN_("%1X"), low2);
|
||||
} else {
|
||||
sprintf(line1 + l1, "%1X", low1);
|
||||
sprintf(line2 + l2, "%1X", low2);
|
||||
}
|
||||
}
|
||||
DbpString(line1);
|
||||
DbpString(line2);
|
||||
}
|
||||
|
||||
void iClass_TearBlock(iclass_tearblock_req_t *msg) {
|
||||
|
||||
if (msg == NULL) {
|
||||
reply_ng(CMD_HF_ICLASS_TEARBL, PM3_ESOFT, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// local variable copies
|
||||
int tear_start = msg->tear_start;
|
||||
int tear_end = msg->tear_end;
|
||||
int tear_inc = msg->increment;
|
||||
int tear_loop = msg->tear_loop;
|
||||
|
||||
int loop_count = 0;
|
||||
|
||||
uint32_t start_time = 0;
|
||||
uint32_t eof_time = 0;
|
||||
|
||||
int isok = PM3_SUCCESS;
|
||||
|
||||
uint8_t data[8] = {0};
|
||||
memcpy(data, msg->data, sizeof(data));
|
||||
|
||||
uint8_t mac[4] = {0};
|
||||
memcpy(mac, msg->mac, sizeof(mac));
|
||||
|
||||
picopass_hdr_t hdr = {0};
|
||||
iclass_auth_req_t req = {
|
||||
.blockno = msg->req.blockno,
|
||||
.do_auth = msg->req.do_auth,
|
||||
.send_reply = msg->req.send_reply,
|
||||
.shallow_mod = msg->req.shallow_mod,
|
||||
.use_credit_key = msg->req.use_credit_key,
|
||||
.use_elite = msg->req.use_elite,
|
||||
.use_raw = msg->req.use_raw,
|
||||
.use_replay = msg->req.use_replay
|
||||
};
|
||||
memcpy(req.key, msg->req.key, PICOPASS_BLOCK_SIZE);
|
||||
|
||||
LED_A_ON();
|
||||
Iso15693InitReader();
|
||||
|
||||
// save old debug log level
|
||||
int oldbg = g_dbglevel;
|
||||
|
||||
// no debug logging please
|
||||
g_dbglevel = DBG_NONE;
|
||||
|
||||
// select
|
||||
bool res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
|
||||
if (res == false) {
|
||||
DbpString(_RED_("Failed to select iClass tag"));
|
||||
isok = PM3_ECARDEXCHANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// authenticate
|
||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, mac);
|
||||
if (res == false) {
|
||||
DbpString(_RED_("Failed to authenticate with iClass tag"));
|
||||
isok = PM3_ECARDEXCHANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
uint8_t data_read_orig[PICOPASS_BLOCK_SIZE] = {0};
|
||||
|
||||
// read block
|
||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
res = iclass_read_block(req.blockno, data_read_orig, &start_time, &eof_time, req.shallow_mod);
|
||||
if (res == false) {
|
||||
Dbprintf("Failed to read block %u", req.blockno);
|
||||
isok = PM3_ECARDEXCHANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bool erase_phase = false;
|
||||
bool read_ok = false;
|
||||
|
||||
// static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
uint8_t ff_data[PICOPASS_BLOCK_SIZE] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
uint8_t data_read[PICOPASS_BLOCK_SIZE] = {0};
|
||||
|
||||
// create READ command
|
||||
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, req.blockno, 0x00, 0x00};
|
||||
AddCrc(cmd_read + 1, 1);
|
||||
|
||||
// create WRITE COMMAND and new block data
|
||||
uint8_t cmd_write[14] = { 0x80 | ICLASS_CMD_UPDATE, req.blockno };
|
||||
uint8_t cmd_write_len = 14;
|
||||
memcpy(cmd_write + 2, data, PICOPASS_BLOCK_SIZE);
|
||||
|
||||
uint8_t pagemap = get_pagemap(&hdr);
|
||||
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
||||
// Unsecured tags uses CRC16, but don't include the UPDATE operation code
|
||||
// byte0 = update op
|
||||
// byte1 = block no
|
||||
// byte2..9 = new block data
|
||||
AddCrc(cmd_write + 1, 9);
|
||||
cmd_write_len -= 2;
|
||||
} else {
|
||||
|
||||
if (req.use_replay) {
|
||||
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||
} else {
|
||||
// Secure tags uses MAC
|
||||
uint8_t wb[9];
|
||||
wb[0] = req.blockno;
|
||||
memcpy(wb + 1, data, PICOPASS_BLOCK_SIZE);
|
||||
|
||||
if (req.use_credit_key)
|
||||
doMAC_N(wb, sizeof(wb), hdr.key_c, mac);
|
||||
else
|
||||
doMAC_N(wb, sizeof(wb), hdr.key_d, mac);
|
||||
|
||||
memcpy(cmd_write + 10, mac, sizeof(mac));
|
||||
}
|
||||
}
|
||||
|
||||
// Main loop
|
||||
while ((tear_start <= tear_end) && (read_ok == false)) {
|
||||
|
||||
if (BUTTON_PRESS() || data_available()) {
|
||||
isok = PM3_EOPABORTED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// set tear off trigger
|
||||
g_tearoff_enabled = true;
|
||||
g_tearoff_delay_us = (tear_start & 0xFFFF);
|
||||
|
||||
if (tear_loop > 1) {
|
||||
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us - " _YELLOW_("%3u") " iter", tear_start, tear_end, loop_count + 1);
|
||||
} else {
|
||||
DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us", tear_start, tear_end);
|
||||
}
|
||||
|
||||
// write block
|
||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod);
|
||||
|
||||
tearoff_hook();
|
||||
|
||||
switch_off();
|
||||
|
||||
// start reading block
|
||||
|
||||
// reinit
|
||||
Iso15693InitReader();
|
||||
|
||||
// select tag
|
||||
res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod);
|
||||
if (res == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip authentication for config and e-purse blocks (1,2)
|
||||
if (req.blockno > 2) {
|
||||
|
||||
// authenticate
|
||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, NULL);
|
||||
if (res == false) {
|
||||
DbpString("Failed to authenticate after tear");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// read again and keep field on
|
||||
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
res = iclass_read_block(req.blockno, data_read, &start_time, &eof_time, req.shallow_mod);
|
||||
if (res == false) {
|
||||
DbpString("Failed to read block after tear");
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
bool tear_success = true;
|
||||
|
||||
if (memcmp(data_read, data, PICOPASS_BLOCK_SIZE) != 0) {
|
||||
tear_success = false;
|
||||
}
|
||||
|
||||
if ((tear_success == false) &&
|
||||
(memcmp(data_read, zeros, PICOPASS_BLOCK_SIZE) != 0) &&
|
||||
(memcmp(data_read, data_read_orig, PICOPASS_BLOCK_SIZE) != 0)) {
|
||||
|
||||
// tearoff succeeded (partially)
|
||||
|
||||
if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 &&
|
||||
memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) {
|
||||
|
||||
erase_phase = true;
|
||||
DbpString("");
|
||||
DbpString(_CYAN_("Erase phase hit... ALL ONES"));
|
||||
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
|
||||
} 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 (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 more OTP bits set..
|
||||
if (data_read[1] > data_read_orig[1] ||
|
||||
data_read[2] > data_read_orig[2]) {
|
||||
DbpString("");
|
||||
DbpString("More OTP bits got set!!!");
|
||||
|
||||
// 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();
|
||||
|
||||
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 (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!");
|
||||
}
|
||||
|
||||
loop_count++;
|
||||
|
||||
// increase tear off delay
|
||||
if (loop_count == tear_loop) {
|
||||
tear_start += tear_inc;
|
||||
loop_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
switch_off();
|
||||
|
||||
// reset tear off trigger
|
||||
g_tearoff_enabled = false;
|
||||
|
||||
// restore debug message levels
|
||||
g_dbglevel = oldbg;
|
||||
|
||||
if (msg->req.send_reply) {
|
||||
reply_ng(CMD_HF_ICLASS_TEARBL, isok, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void iClass_Restore(iclass_restore_req_t *msg) {
|
||||
|
||||
// sanitation
|
||||
|
@ -2292,8 +2704,6 @@ void iClass_Recover(iclass_recover_req_t *msg) {
|
|||
|
||||
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
|
||||
uint8_t blockno = 24;
|
||||
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
|
||||
AddCrc(cmd_read + 1, 1);
|
||||
int priv_esc_tries = 0;
|
||||
bool priv_esc = false;
|
||||
while (!priv_esc) {
|
||||
|
|
|
@ -72,4 +72,5 @@ uint8_t get_pagemap(const picopass_hdr_t *hdr);
|
|||
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
|
||||
|
||||
void iClass_Recover(iclass_recover_req_t *msg);
|
||||
void iClass_TearBlock(iclass_tearblock_req_t *msg);
|
||||
#endif
|
||||
|
|
|
@ -2135,6 +2135,9 @@ static int iso14443b_select_picopass_card(picopass_hdr_t *hdr) {
|
|||
static uint8_t act_all[] = { ICLASS_CMD_ACTALL };
|
||||
static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY };
|
||||
static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 };
|
||||
|
||||
// ICLASS_CMD_SELECT 0x81 tells ISO14443b/BPSK coding/106 kbits/s
|
||||
// ICLASS_CMD_SELECT 0x41 tells ISO14443b/BPSK coding/423 kbits/s
|
||||
uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64};
|
||||
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 };
|
||||
|
|
|
@ -2985,6 +2985,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|||
arg_int0("e", NULL, "<dec>", "tearoff delay end (in us) must be a higher value than the start delay"),
|
||||
arg_int0(NULL, "loop", "<dec>", "number of times to loop per tearoff time"),
|
||||
arg_int0(NULL, "sleep", "<ms>", "Sleep between each tear"),
|
||||
arg_lit0(NULL, "arm", "Runs the commands on device side and tries to stabilize tears"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
|
@ -3017,6 +3018,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|||
int tearoff_end = arg_get_int_def(ctx, 14, tearoff_start + tearoff_increment + 500);
|
||||
int tearoff_loop = arg_get_int_def(ctx, 15, 1);
|
||||
int tearoff_sleep = arg_get_int_def(ctx, 16, 0);
|
||||
bool run_on_device = arg_get_lit(ctx, 17);
|
||||
|
||||
CLIParserFree(ctx);
|
||||
|
||||
|
@ -3081,7 +3083,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|||
}
|
||||
|
||||
int loop_count = 0;
|
||||
int isok = 0;
|
||||
int isok = PM3_SUCCESS;
|
||||
bool read_ok = false;
|
||||
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
|
||||
if (use_credit_key) {
|
||||
|
@ -3188,52 +3190,58 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|||
// clear trace log
|
||||
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
|
||||
|
||||
PrintAndLogEx(INFO, "---------------------------------------");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
||||
// Main loop
|
||||
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
|
||||
if (run_on_device) {
|
||||
|
||||
if (kbd_enter_pressed()) {
|
||||
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
||||
isok = PM3_EOPABORTED;
|
||||
goto out;
|
||||
}
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "---------------------------------------");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
||||
|
||||
// set tear off trigger
|
||||
clearCommandBuffer();
|
||||
tearoff_params_t params = {
|
||||
.delay_us = (tearoff_start & 0xFFFF),
|
||||
.on = true,
|
||||
.off = false
|
||||
iclass_tearblock_req_t payload = {
|
||||
.req.use_raw = rawkey,
|
||||
.req.use_elite = elite,
|
||||
.req.use_credit_key = use_credit_key,
|
||||
.req.use_replay = use_replay,
|
||||
.req.blockno = blockno,
|
||||
.req.send_reply = true,
|
||||
.req.do_auth = auth,
|
||||
.req.shallow_mod = shallow_mod,
|
||||
.tear_start = tearoff_start,
|
||||
.tear_end = tearoff_end,
|
||||
.increment = tearoff_increment,
|
||||
.tear_loop = tearoff_loop,
|
||||
};
|
||||
memcpy(payload.req.key, key, PICOPASS_BLOCK_SIZE);
|
||||
memcpy(payload.data, data, sizeof(payload.data));
|
||||
memcpy(payload.mac, mac, sizeof(payload.mac));
|
||||
|
||||
int res = handle_tearoff(¶ms, verbose);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "Failed to configure tear off");
|
||||
isok = PM3_ESOFT;
|
||||
goto out;
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_ICLASS_TEARBL, (uint8_t *)&payload, sizeof(payload));
|
||||
|
||||
if (WaitForResponseTimeout(CMD_HF_ICLASS_TEARBL, &resp, 1000)) {
|
||||
if (resp.status == PM3_EOPABORTED) {
|
||||
PrintAndLogEx(DEBUG, "Button pressed, user aborted");
|
||||
isok = resp.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (tearoff_loop > 1) {
|
||||
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1);
|
||||
} else {
|
||||
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF));
|
||||
}
|
||||
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.
|
||||
// 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);
|
||||
} else {
|
||||
|
||||
// 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) {
|
||||
PrintAndLogEx(INFO, "---------------------------------------");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
||||
// Main loop
|
||||
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
|
||||
|
||||
if (kbd_enter_pressed()) {
|
||||
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
||||
|
@ -3241,159 +3249,202 @@ static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|||
goto out;
|
||||
}
|
||||
|
||||
// skip authentication for config and e-purse blocks (1,2)
|
||||
if (blockno < 3) {
|
||||
read_auth = false;
|
||||
}
|
||||
// set tear off trigger
|
||||
clearCommandBuffer();
|
||||
tearoff_params_t params = {
|
||||
.delay_us = (tearoff_start & 0xFFFF),
|
||||
.on = true,
|
||||
.off = false
|
||||
};
|
||||
|
||||
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false);
|
||||
if (res == PM3_SUCCESS && !reread) {
|
||||
if (memcmp(data_read, zeros, 8) == 0) {
|
||||
reread = true;
|
||||
} else {
|
||||
first_read = true;
|
||||
reread = false;
|
||||
}
|
||||
} else if (res == PM3_SUCCESS && reread) {
|
||||
first_read = true;
|
||||
reread = false;
|
||||
} else if (res != PM3_SUCCESS) {
|
||||
decrease = true;
|
||||
}
|
||||
|
||||
readcount++;
|
||||
}
|
||||
|
||||
if (readcount > 1) {
|
||||
PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount);
|
||||
}
|
||||
|
||||
// if there was an error reading repeat the tearoff with the same delay
|
||||
if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) {
|
||||
tearoff_start -= tearoff_increment;
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start);
|
||||
}
|
||||
}
|
||||
|
||||
bool tear_success = true;
|
||||
bool expected_values = true;
|
||||
|
||||
if (memcmp(data_read, data, 8) != 0) {
|
||||
tear_success = false;
|
||||
}
|
||||
|
||||
if ((tear_success == false) && (memcmp(data_read, zeros, 8) != 0) && (memcmp(data_read, data_read_orig, 8) != 0)) {
|
||||
|
||||
// tearoff succeeded (partially)
|
||||
|
||||
expected_values = false;
|
||||
|
||||
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||
erase_phase = true;
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
} else {
|
||||
|
||||
if (erase_phase) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
} else {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
}
|
||||
}
|
||||
|
||||
bool goto_out = false;
|
||||
if (blockno == 2) {
|
||||
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||
PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok"));
|
||||
PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`");
|
||||
PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`");
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockno == 1) {
|
||||
if (data_read[0] != data_read_orig[0]) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]);
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
|
||||
if (data_read[7] != data_read_orig[7]) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]);
|
||||
|
||||
const char *flag_names[8] = {
|
||||
"RA",
|
||||
"Fprod0",
|
||||
"Fprod1",
|
||||
"Crypt0 (*1)",
|
||||
"Crypt1 (*0)",
|
||||
"Coding0",
|
||||
"Coding1",
|
||||
"Fpers (*1)"
|
||||
};
|
||||
PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed");
|
||||
PrintAndLogEx(INFO, "---------------------------------------");
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
int bit1 = (data_read_orig[7] >> i) & 1;
|
||||
int bit2 = (data_read[7] >> i) & 1;
|
||||
PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2);
|
||||
}
|
||||
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
|
||||
// if more OTP bits set..
|
||||
if (data_read[1] > data_read_orig[1] ||
|
||||
data_read[2] > data_read_orig[2]) {
|
||||
PrintAndLogEx(SUCCESS, "More OTP bits got set!!!");
|
||||
|
||||
data_read[7] = 0xBC;
|
||||
res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )" );
|
||||
}
|
||||
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (goto_out) {
|
||||
int res = handle_tearoff(¶ms, verbose);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "Failed to configure tear off");
|
||||
isok = PM3_ESOFT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (tear_success) { // tearoff succeeded with expected values
|
||||
if (tearoff_loop > 1) {
|
||||
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" iter", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1);
|
||||
} else {
|
||||
PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF));
|
||||
}
|
||||
|
||||
read_ok = true;
|
||||
tear_success = true;
|
||||
// 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);
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
|
||||
, sprint_hex_inrow(data_read, sizeof(data_read)),
|
||||
(expected_values) ? _GREEN_(" -> Expected values!") : ""
|
||||
);
|
||||
}
|
||||
// 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) {
|
||||
|
||||
loop_count++;
|
||||
if (kbd_enter_pressed()) {
|
||||
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
||||
isok = PM3_EOPABORTED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (loop_count == tearoff_loop) {
|
||||
tearoff_start += tearoff_increment;
|
||||
loop_count = 0;
|
||||
}
|
||||
// skip authentication for config and e-purse blocks (1,2)
|
||||
if (blockno < 3) {
|
||||
read_auth = false;
|
||||
}
|
||||
|
||||
if (tearoff_sleep) {
|
||||
msleep(tearoff_sleep);
|
||||
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false);
|
||||
if (res == PM3_SUCCESS && !reread) {
|
||||
if (memcmp(data_read, zeros, 8) == 0) {
|
||||
reread = true;
|
||||
} else {
|
||||
first_read = true;
|
||||
reread = false;
|
||||
}
|
||||
} else if (res == PM3_SUCCESS && reread) {
|
||||
first_read = true;
|
||||
reread = false;
|
||||
} else if (res != PM3_SUCCESS) {
|
||||
decrease = true;
|
||||
}
|
||||
|
||||
readcount++;
|
||||
}
|
||||
|
||||
if (readcount > 1) {
|
||||
PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount);
|
||||
}
|
||||
|
||||
// if there was an error reading repeat the tearoff with the same delay
|
||||
if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) {
|
||||
tearoff_start -= tearoff_increment;
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start);
|
||||
}
|
||||
}
|
||||
|
||||
bool tear_success = true;
|
||||
bool expected_values = true;
|
||||
|
||||
if (memcmp(data_read, data, 8) != 0) {
|
||||
tear_success = false;
|
||||
}
|
||||
|
||||
if ((tear_success == false) &&
|
||||
(memcmp(data_read, zeros, 8) != 0) &&
|
||||
(memcmp(data_read, data_read_orig, 8) != 0)) {
|
||||
|
||||
// tearoff succeeded (partially)
|
||||
|
||||
expected_values = false;
|
||||
|
||||
if (memcmp(data_read, ff_data, 8) == 0 &&
|
||||
memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||
erase_phase = true;
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
} else {
|
||||
|
||||
if (erase_phase) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
} else {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase"));
|
||||
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
||||
}
|
||||
}
|
||||
|
||||
bool goto_out = false;
|
||||
if (blockno == 2) {
|
||||
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
|
||||
PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok"));
|
||||
PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`");
|
||||
PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`");
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockno == 1) {
|
||||
if (data_read[0] != data_read_orig[0]) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]);
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
|
||||
if (data_read[7] != data_read_orig[7]) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]);
|
||||
|
||||
const char *flag_names[8] = {
|
||||
"RA",
|
||||
"Fprod0",
|
||||
"Fprod1",
|
||||
"Crypt0 (*1)",
|
||||
"Crypt1 (*0)",
|
||||
"Coding0",
|
||||
"Coding1",
|
||||
"Fpers (*1)"
|
||||
};
|
||||
PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed");
|
||||
PrintAndLogEx(INFO, "---------------------------------------");
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
int bit1 = (data_read_orig[7] >> i) & 1;
|
||||
int bit2 = (data_read[7] >> i) & 1;
|
||||
PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2);
|
||||
}
|
||||
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
|
||||
// if more OTP bits set..
|
||||
if (data_read[1] > data_read_orig[1] ||
|
||||
data_read[2] > data_read_orig[2]) {
|
||||
PrintAndLogEx(SUCCESS, "More OTP bits got set!!!");
|
||||
|
||||
data_read[7] = 0xBC;
|
||||
res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )");
|
||||
}
|
||||
|
||||
isok = PM3_SUCCESS;
|
||||
goto_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (goto_out) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (tear_success) { // tearoff succeeded with expected values
|
||||
|
||||
read_ok = true;
|
||||
tear_success = true;
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s"
|
||||
, sprint_hex_inrow(data_read, sizeof(data_read)),
|
||||
(expected_values) ? _GREEN_(" -> Expected values!") : ""
|
||||
);
|
||||
}
|
||||
|
||||
loop_count++;
|
||||
|
||||
if (loop_count == tearoff_loop) {
|
||||
tearoff_start += tearoff_increment;
|
||||
loop_count = 0;
|
||||
}
|
||||
|
||||
if (tearoff_sleep) {
|
||||
msleep(tearoff_sleep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,17 @@ typedef struct {
|
|||
uint8_t mac[4];
|
||||
} PACKED iclass_writeblock_req_t;
|
||||
|
||||
// iCLASS tearoff block request data structure
|
||||
typedef struct {
|
||||
iclass_auth_req_t req;
|
||||
uint8_t data[8];
|
||||
uint8_t mac[4];
|
||||
int tear_start;
|
||||
int tear_end;
|
||||
int increment;
|
||||
int tear_loop;
|
||||
} PACKED iclass_tearblock_req_t;
|
||||
|
||||
// iCLASS write block request data structure
|
||||
typedef struct {
|
||||
iclass_auth_req_t req;
|
||||
|
|
|
@ -668,6 +668,7 @@ typedef struct {
|
|||
#define CMD_HF_ICLASS_RESTORE 0x039B
|
||||
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
|
||||
#define CMD_HF_ICLASS_RECOVER 0x039D
|
||||
#define CMD_HF_ICLASS_TEARBL 0x039E
|
||||
|
||||
|
||||
// For ISO1092 / FeliCa
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue