From 186308cb4ad6ac5887cddc430c838ae1affb7ebe Mon Sep 17 00:00:00 2001 From: nvx Date: Sun, 10 Sep 2023 22:56:46 +1000 Subject: [PATCH] Add `hf iclass creditepurse` command to allow crediting the epurse debit value. --- CHANGELOG.md | 1 + armsrc/appmain.c | 4 + armsrc/iclass.c | 157 +++++++++++++++++++++++++++++++- armsrc/iclass.h | 1 + client/src/cmdhficlass.c | 124 +++++++++++++++++++++++++ client/src/pm3line_vocabulary.h | 1 + doc/commands.json | 26 +++++- doc/commands.md | 1 + include/iclass_cmd.h | 6 ++ include/pm3_cmd.h | 3 +- 10 files changed, 318 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1803239cbc..d85eb7e2b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 900cb65175..42d551d9bf 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -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 diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 5a68a8207b..953cfaa6f6 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -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; @@ -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 diff --git a/armsrc/iclass.h b/armsrc/iclass.h index ebbda2e9f2..363c042ab2 100644 --- a/armsrc/iclass.h +++ b/armsrc/iclass.h @@ -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); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 36144abbe7..86dda89cd8 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -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", "", "Credit key as 8 hex bytes"), + arg_int0(NULL, "ki", "", "Key index to select key from memory 'hf iclass managekeys'"), + arg_str1("d", "data", "", "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", @@ -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"}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 8926ffb948..5cbe8b4952 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -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" }, diff --git a/doc/commands.json b/doc/commands.json index bbe8f8b935..55da69cd3b 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3029,6 +3029,26 @@ ], "usage": "hf iclass configcard [-hglp] [--ci ] [--ki ]" }, + "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 Credit key as 8 hex bytes", + "--ki Key index to select key from memory 'hf iclass managekeys'", + "-d, --data 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 ] [--ki ] -d [--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", @@ -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 " }, @@ -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" } } \ No newline at end of file diff --git a/doc/commands.md b/doc/commands.md index 919ce3f8e3..0c94700e65 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -415,6 +415,7 @@ Check column "offline" for their availability. |`hf iclass sniff `|N |`Eavesdrop Picopass / iCLASS communication` |`hf iclass view `|Y |`Display content from tag dump file` |`hf iclass wrbl `|N |`Write Picopass / iCLASS block` +|`hf iclass creditepurse `|N |`Credit epurse value` |`hf iclass chk `|N |`Check keys` |`hf iclass loclass `|Y |`Use loclass to perform bruteforce reader attack` |`hf iclass lookup `|Y |`Uses authentication trace to check for key in dictionary file` diff --git a/include/iclass_cmd.h b/include/iclass_cmd.h index bc7c1e6cac..dca08b6aab 100644 --- a/include/iclass_cmd.h +++ b/include/iclass_cmd.h @@ -87,6 +87,12 @@ typedef struct { uint8_t mac[4]; } PACKED iclass_writeblock_req_t; +// iCLASS write block request data structure +typedef struct { + iclass_auth_req_t req; + uint8_t epurse[4]; +} PACKED iclass_credit_epurse_t; + // iCLASS dump data structure typedef struct { uint8_t blockno; diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index a96a16c05e..e84a4183b5 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -565,7 +565,7 @@ typedef struct { #define CMD_HF_EPA_COLLECT_NONCE 0x038A #define CMD_HF_EPA_REPLAY 0x038B -#define CMD_HF_EPA_PACE_SIMULATE 0x039C +#define CMD_HF_EPA_PACE_SIMULATE 0x038C #define CMD_HF_LEGIC_INFO 0x03BC #define CMD_HF_LEGIC_ESET 0x03BD @@ -581,6 +581,7 @@ typedef struct { #define CMD_HF_ICLASS_EML_MEMSET 0x0398 #define CMD_HF_ICLASS_CHKKEYS 0x039A #define CMD_HF_ICLASS_RESTORE 0x039B +#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C // For ISO1092 / FeliCa #define CMD_HF_FELICA_SIMULATE 0x03A0