Skip to content

Commit

Permalink
Merge pull request #2106 from nvx/feature/iclass_epurse_credit
Browse files Browse the repository at this point in the history
Add `hf iclass creditepurse` command to allow crediting the epurse debit value
  • Loading branch information
iceman1001 authored Sep 10, 2023
2 parents 914a04d + 186308c commit 9a5c262
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
- Added `hf iclass creditepurse` command to allow crediting the epurse debit value (@nvx)

## [Raccoon.4.17140][2023-09-09]
- Changed text and adjust pm3_test case for mf_aes_brute (@doegox)
Expand Down
4 changes: 4 additions & 0 deletions armsrc/appmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,10 @@ static void PacketReceived(PacketCommandNG *packet) {
iClass_Restore((iclass_restore_req_t *)packet->data.asBytes);
break;
}
case CMD_HF_ICLASS_CREDIT_EPURSE: {
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
break;
}
#endif

#ifdef WITH_HFSNIFF
Expand Down
157 changes: 155 additions & 2 deletions armsrc/iclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -1902,13 +1902,13 @@ void iClass_WriteBlock(uint8_t *msg) {

// verify write
uint8_t all_ff[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
if (payload->req.blockno == 2) {
if (pagemap == PICOPASS_SECURE_PAGEMODE && payload->req.blockno == 2) {
// check response. e-purse update swaps first and second half
if (memcmp(payload->data + 4, resp, 4) || memcmp(payload->data, resp + 4, 4)) {
res = false;
goto out;
}
} else if (payload->req.blockno == 3 || payload->req.blockno == 4) {
} else if (pagemap == PICOPASS_SECURE_PAGEMODE && (payload->req.blockno == 3 || payload->req.blockno == 4)) {
// check response. Key updates always return 0xffffffffffffffff
if (memcmp(all_ff, resp, 8)) {
res = false;
Expand All @@ -1929,6 +1929,159 @@ void iClass_WriteBlock(uint8_t *msg) {
reply_ng(CMD_HF_ICLASS_WRITEBL, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
}

void iclass_credit_epurse(iclass_credit_epurse_t *payload) {

LED_A_ON();

bool shallow_mod = payload->req.shallow_mod;

Iso15693InitReader();

// select tag.
uint32_t eof_time = 0;
picopass_hdr_t hdr = {0};
uint8_t res = select_iclass_tag(&hdr, payload->req.use_credit_key, &eof_time, shallow_mod);
if (res == false) {
goto out;
}

uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;

uint8_t mac[4] = {0};

// authenticate
if (payload->req.do_auth) {

res = authenticate_iclass_tag(&payload->req, &hdr, &start_time, &eof_time, mac);
if (res == false) {
goto out;
}
}

start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;

uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, payload->req.blockno, 0x00, 0x00};
AddCrc(cmd_read + 1, 1);

uint8_t epurse[10];
res = iclass_send_cmd_with_retries(cmd_read, sizeof(cmd_read), epurse, sizeof(epurse), 10, 3, &start_time, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, shallow_mod);
if (!res) {
switch_off();
if (payload->req.send_reply)
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_ETIMEOUT, (uint8_t *)&res, sizeof(uint8_t));
return;
}

uint8_t write[14] = { 0x80 | ICLASS_CMD_UPDATE, payload->req.blockno };
uint8_t write_len = 14;

uint8_t epurse_offset = 0;
const uint8_t empty_epurse[] = {0xff, 0xff, 0xff, 0xff};
if (!memcmp(epurse, empty_epurse, 4)) {
// epurse data in stage 2
epurse_offset = 4;
}

memcpy(epurse + epurse_offset, payload->epurse, 4);

// blank out debiting value as per the first step of the crediting procedure
epurse[epurse_offset + 0] = 0xFF;
epurse[epurse_offset + 1] = 0xFF;

// initial epurse write for credit
memcpy(write + 2, epurse, 8);

doMAC_N(write + 1, 9, payload->req.use_credit_key ? hdr.key_c : hdr.key_d, mac);
memcpy(write + 10, mac, sizeof(mac));

start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;

uint8_t resp[10] = {0};

uint8_t tries = 3;
while (tries-- > 0) {

iclass_send_as_reader(write, write_len, &start_time, &eof_time, shallow_mod);

if (tearoff_hook() == PM3_ETEAROFF) { // tearoff occurred
res = false;
switch_off();
if (payload->req.send_reply)
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_ETEAROFF, (uint8_t *)&res, sizeof(uint8_t));
return;
} else {

uint16_t resp_len = 0;
int res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true, &resp_len);
if (res2 == PM3_SUCCESS && resp_len == 10) {
res = true;
break;
}
}
}

if (tries == 0) {
res = false;
goto out;
}

// check response. e-purse update swaps first and second half
if (memcmp(write + 2 + 4, resp, 4) || memcmp(write + 2, resp + 4, 4)) {
res = false;
goto out;
}

// new epurse write
// epurse offset is now flipped after the first write
epurse_offset ^= 4;
memcpy(resp + epurse_offset, payload->epurse, 4);
memcpy(write + 2, resp, 8);

doMAC_N(write + 1, 9, payload->req.use_credit_key ? hdr.key_c : hdr.key_d, mac);
memcpy(write + 10, mac, sizeof(mac));

start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;

tries = 3;
while (tries-- > 0) {

iclass_send_as_reader(write, write_len, &start_time, &eof_time, shallow_mod);

if (tearoff_hook() == PM3_ETEAROFF) { // tearoff occurred
res = false;
switch_off();
if (payload->req.send_reply)
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_ETEAROFF, (uint8_t *)&res, sizeof(uint8_t));
return;
} else {

uint16_t resp_len = 0;
int res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true, &resp_len);
if (res2 == PM3_SUCCESS && resp_len == 10) {
res = true;
break;
}
}
}

if (tries == 0) {
res = false;
goto out;
}

// check response. e-purse update swaps first and second half
if (memcmp(write + 2 + 4, resp, 4) || memcmp(write + 2, resp + 4, 4)) {
res = false;
goto out;
}

out:
switch_off();

if (payload->req.send_reply)
reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t));
}

void iClass_Restore(iclass_restore_req_t *msg) {

// sanitation
Expand Down
1 change: 1 addition & 0 deletions armsrc/iclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ void SniffIClass(uint8_t jam_search_len, uint8_t *jam_search_string);
void ReaderIClass(uint8_t flags);

void iClass_WriteBlock(uint8_t *msg);
void iclass_credit_epurse(iclass_credit_epurse_t *payload);
void iClass_Dump(uint8_t *msg);

void iClass_Restore(iclass_restore_req_t *msg);
Expand Down
124 changes: 124 additions & 0 deletions client/src/cmdhficlass.c
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,129 @@ static int CmdHFiClass_WriteBlock(const char *Cmd) {
return isok;
}

static int CmdHFiClassCreditEpurse(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass creditepurse",
"Credit the epurse on an iCLASS tag. The provided key must be the credit key.\n"
"The first two bytes of the epurse are the debit value (big endian) and may be any value except FFFF.\n"
"The remaining two bytes of the epurse are the credit value and must be smaller than the previous value.",
"hf iclass creditepurse -d FEFFFFFF -k 001122334455667B\n"
"hf iclass creditepurse -d FEFFFFFF --ki 0");

void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "Credit key as 8 hex bytes"),
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
arg_lit0(NULL, "elite", "elite computations applied to key"),
arg_lit0(NULL, "raw", "no computations applied to key"),
arg_lit0("v", "verbose", "verbose output"),
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);

int key_len = 0;
uint8_t key[8] = {0};

CLIGetHexWithReturn(ctx, 1, key, &key_len);

int key_nr = arg_get_int_def(ctx, 2, -1);

if (key_len > 0 && key_nr >= 0) {
PrintAndLogEx(ERR, "Please specify key or index, not both");
CLIParserFree(ctx);
return PM3_EINVARG;
}

if (key_len > 0) {
if (key_len != 8) {
PrintAndLogEx(ERR, "Key is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
} else if (key_nr >= 0) {
if (key_nr < ICLASS_KEYS_MAX) {
memcpy(key, iClass_Key_Table[key_nr], 8);
PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
} else {
PrintAndLogEx(ERR, "Key number is invalid");
CLIParserFree(ctx);
return PM3_EINVARG;
}
} else {
PrintAndLogEx(ERR, "Key or key number must be provided");
CLIParserFree(ctx);
return PM3_EINVARG;
}

int blockno = 2;

int data_len = 0;
uint8_t data[4] = {0};
CLIGetHexWithReturn(ctx, 3, data, &data_len);

if (data_len != 4) {
PrintAndLogEx(ERR, "Data must be 4 hex bytes (8 hex symbols)");
CLIParserFree(ctx);
return PM3_EINVARG;
}

bool elite = arg_get_lit(ctx, 4);
bool rawkey = arg_get_lit(ctx, 5);
bool verbose = arg_get_lit(ctx, 6);
bool shallow_mod = arg_get_lit(ctx, 7);

CLIParserFree(ctx);

if ((rawkey + elite) > 1) {
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw'");
return PM3_EINVARG;
}

iclass_credit_epurse_t payload = {
.req.use_raw = rawkey,
.req.use_elite = elite,
.req.use_credit_key = true,
.req.use_replay = false,
.req.blockno = blockno,
.req.send_reply = true,
.req.do_auth = true,
.req.shallow_mod = shallow_mod,
};
memcpy(payload.req.key, key, 8);
memcpy(payload.epurse, data, sizeof(payload.epurse));

clearCommandBuffer();
SendCommandNG(CMD_HF_ICLASS_CREDIT_EPURSE, (uint8_t *)&payload, sizeof(payload));
PacketResponseNG resp;

int isok;
if (WaitForResponseTimeout(CMD_HF_ICLASS_CREDIT_EPURSE, &resp, 2000) == 0) {
if (verbose) PrintAndLogEx(WARNING, "Command execute timeout");
isok = PM3_ETIMEOUT;
} else if (resp.status != PM3_SUCCESS) {
if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
isok = resp.status;
} else {
isok = (resp.data.asBytes[0] == 1) ? PM3_SUCCESS : PM3_ESOFT;
}

switch (isok) {
case PM3_SUCCESS:
PrintAndLogEx(SUCCESS, "Credited epurse successfully");
break;
case PM3_ETEAROFF:
if (verbose)
PrintAndLogEx(INFO, "Writing tear off triggered");
break;
default:
PrintAndLogEx(FAILED, "Writing failed");
break;
}
return isok;
}

static int CmdHFiClassRestore(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass restore",
Expand Down Expand Up @@ -4278,6 +4401,7 @@ static command_t CommandTable[] = {
{"sniff", CmdHFiClassSniff, IfPm3Iclass, "Eavesdrop Picopass / iCLASS communication"},
{"view", CmdHFiClassView, AlwaysAvailable, "Display content from tag dump file"},
{"wrbl", CmdHFiClass_WriteBlock, IfPm3Iclass, "Write Picopass / iCLASS block"},
{"creditepurse", CmdHFiClassCreditEpurse, IfPm3Iclass, "Credit epurse value"},
{"-----------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("recovery") " --------------------"},
// {"autopwn", CmdHFiClassAutopwn, IfPm3Iclass, "Automatic key recovery tool for iCLASS"},
{"chk", CmdHFiClassCheckKeys, IfPm3Iclass, "Check keys"},
Expand Down
1 change: 1 addition & 0 deletions client/src/pm3line_vocabulary.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ const static vocabulary_t vocabulary[] = {
{ 0, "hf iclass sniff" },
{ 1, "hf iclass view" },
{ 0, "hf iclass wrbl" },
{ 0, "hf iclass creditepurse" },
{ 0, "hf iclass chk" },
{ 1, "hf iclass loclass" },
{ 1, "hf iclass lookup" },
Expand Down
26 changes: 23 additions & 3 deletions doc/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -3029,6 +3029,26 @@
],
"usage": "hf iclass configcard [-hglp] [--ci <dec>] [--ki <dec>]"
},
"hf iclass creditepurse": {
"command": "hf iclass creditepurse",
"description": "Credit the epurse on an iCLASS tag. The provided key must be the credit key. The first two bytes of the epurse are the debit value (big endian) and may be any value except FFFF. The remaining two bytes of the epurse are the credit value and must be smaller than the previous value.",
"notes": [
"hf iclass creditepurse -d FEFFFFFF -k 001122334455667B",
"hf iclass creditepurse -d FEFFFFFF --ki 0"
],
"offline": false,
"options": [
"-h, --help This help",
"-k, --key <hex> Credit key as 8 hex bytes",
"--ki <dec> Key index to select key from memory 'hf iclass managekeys'",
"-d, --data <hex> data to write as 8 hex bytes",
"--elite elite computations applied to key",
"--raw no computations applied to key",
"-v, --verbose verbose output",
"--shallow use shallow (ASK) reader modulation instead of OOK"
],
"usage": "hf iclass creditepurse [-hv] [-k <hex>] [--ki <dec>] -d <hex> [--elite] [--raw] [--shallow]"
},
"hf iclass decrypt": {
"command": "hf iclass decrypt",
"description": "3DES decrypt data This is a naive implementation, it tries to decrypt every block after block 6. Correct behaviour would be to decrypt only the application areas where the key is valid, which is defined by the configuration block. OBS! In order to use this function, the file `iclass_decryptionkey.bin` must reside in the resources directory. The file should be 16 bytes binary data or... make sure your cardhelper is placed in the sim module",
Expand Down Expand Up @@ -8786,7 +8806,7 @@
"-1, --ht1 Card type Hitag 1",
"-2, --ht2 Card type Hitag 2",
"-s, --hts Card type Hitag S",
"-m, --htm Card type Hitag \u03bc"
"-m, --htm Card type Hitag \u00ce\u00bc"
],
"usage": "lf hitag eload [-h12sm] -f <fn>"
},
Expand Down Expand Up @@ -11835,8 +11855,8 @@
}
},
"metadata": {
"commands_extracted": 686,
"commands_extracted": 687,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2023-09-07T18:12:46"
"extracted_on": "2023-09-10T12:59:25"
}
}
Loading

0 comments on commit 9a5c262

Please sign in to comment.