From 67770869e9068b99f5db3ebb55185604c6894804 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Wed, 7 Dec 2022 15:02:54 +0800 Subject: [PATCH 1/5] Update iclass_default_keys.dic Added an extra key --- client/dictionaries/iclass_default_keys.dic | 1 + 1 file changed, 1 insertion(+) diff --git a/client/dictionaries/iclass_default_keys.dic b/client/dictionaries/iclass_default_keys.dic index d2d6489b2..cf274e54a 100644 --- a/client/dictionaries/iclass_default_keys.dic +++ b/client/dictionaries/iclass_default_keys.dic @@ -18,6 +18,7 @@ E033CA419AEE43F9 # iCopy-x DRM keys 2020666666668888 # iCL tags 6666202066668888 # iCS tags reversed from the SOs +AFA785A7DAB33378 # from https://youtu.be/EH7dctOPxBA?t=805 # # default picopass KD / Page 0 / Book 1 FDCB5A52EA8F3090 From b57f90b524ed7b69fd7e782ed84970dde51e5fea Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sun, 10 Sep 2023 19:09:18 +0800 Subject: [PATCH 2/5] Update hf_mf_uidbruteforce.lua Fixed issue with how the command is run, old version is invalid as it is missing the -t --- client/luascripts/hf_mf_uidbruteforce.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/luascripts/hf_mf_uidbruteforce.lua b/client/luascripts/hf_mf_uidbruteforce.lua index 4835ba03b..88dca4273 100644 --- a/client/luascripts/hf_mf_uidbruteforce.lua +++ b/client/luascripts/hf_mf_uidbruteforce.lua @@ -124,7 +124,7 @@ local function main(args) local c = string.format( command, n ) print('Running: "'..c..'"') core.console(c) - core.console('msleep '..timeout); + core.console('msleep -t'..timeout); core.console('hw ping') end From 2fb281bdc10094a109135e096ea4a983f5073e88 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sun, 10 Sep 2023 19:11:21 +0800 Subject: [PATCH 3/5] Update iclass_default_keys.dic --- client/dictionaries/iclass_default_keys.dic | 1 - 1 file changed, 1 deletion(-) diff --git a/client/dictionaries/iclass_default_keys.dic b/client/dictionaries/iclass_default_keys.dic index aea6de36a..c717cd88f 100644 --- a/client/dictionaries/iclass_default_keys.dic +++ b/client/dictionaries/iclass_default_keys.dic @@ -18,7 +18,6 @@ E033CA419AEE43F9 # iCopy-x DRM keys 2020666666668888 # iCL tags 6666202066668888 # iCS tags reversed from the SOs -AFA785A7DAB33378 # from https://youtu.be/EH7dctOPxBA?t=805 # # default picopass KD / Page 0 / Book 1 FDCB5A52EA8F3090 From 186308cb4ad6ac5887cddc430c838ae1affb7ebe Mon Sep 17 00:00:00 2001 From: nvx Date: Sun, 10 Sep 2023 22:56:46 +1000 Subject: [PATCH 4/5] 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 1803239cb..d85eb7e2b 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 900cb6517..42d551d9b 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 5a68a8207..953cfaa6f 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 @@ out: 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 ebbda2e9f..363c042ab 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 36144abbe..86dda89cd 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 8926ffb94..5cbe8b495 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 bbe8f8b93..55da69cd3 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 919ce3f8e..0c94700e6 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 bc7c1e6ca..dca08b6aa 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 a96a16c05..e84a4183b 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 From 77258365d45d272e40b3b044b38cc2667bf194e2 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sun, 10 Sep 2023 23:03:53 +0800 Subject: [PATCH 5/5] Update hf_mf_ultimatecard.lua Fixed bug, should be 13 instead of 12 as there are two 12 otherwise and this is referring to the 128b one instead --- client/luascripts/hf_mf_ultimatecard.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/luascripts/hf_mf_ultimatecard.lua b/client/luascripts/hf_mf_ultimatecard.lua index 4bb6034ee..819f68b40 100644 --- a/client/luascripts/hf_mf_ultimatecard.lua +++ b/client/luascripts/hf_mf_ultimatecard.lua @@ -737,7 +737,7 @@ local function set_type(tagtype) write_uid('04112233445566') write_otp('00000000') -- Setting OTP to default 00 00 00 00 write_version('0004030101000b03') -- UL-EV1 (48) 00 04 03 01 01 00 0b 03 - elseif tagtype == 12 then + elseif tagtype == 13 then print('Setting: Ultimate Magic card to UL-EV1 128') connect() send("CF".._key.."F001010000000003000978009102DABC19101011121314151644000000")