mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-06-03 17:31:31 +00:00
9309 lines
349 KiB
C
9309 lines
349 KiB
C
//-----------------------------------------------------------------------------
|
|
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// See LICENSE.txt for the text of the license.
|
|
//-----------------------------------------------------------------------------
|
|
// High frequency iClass commands
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "cmdhficlass.h"
|
|
#include <ctype.h>
|
|
#ifdef _WIN32
|
|
#include <conio.h>
|
|
#else
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include "cliparser.h"
|
|
#include "cmdparser.h" // command_t
|
|
#include "commonutil.h" // ARRAYLEN
|
|
#include "cmdtrace.h"
|
|
#include "util_posix.h"
|
|
#include "comms.h"
|
|
#include "des.h"
|
|
#include "loclass/cipherutils.h"
|
|
#include "loclass/cipher.h"
|
|
#include "loclass/cipher_bs.h"
|
|
#include "loclass/cipher_bs_dispatch.h"
|
|
#include "loclass/ikeys.h"
|
|
#include "loclass/elite_crack.h"
|
|
#include "fileutils.h"
|
|
#include "protocols.h"
|
|
#include "cardhelper.h"
|
|
#include "wiegand_formats.h"
|
|
#include "wiegand_formatutils.h"
|
|
#include "cmdsmartcard.h" // smart select fct
|
|
#include "proxendian.h"
|
|
#include "iclass_cmd.h"
|
|
#include "crypto/asn1utils.h" // ASN1 decoder
|
|
#include "crypto/libpcrypto.h" // aes_encode, aes_decode (for SAM SC)
|
|
#include "preferences.h"
|
|
#include "generator.h"
|
|
#include "cmdhw.h"
|
|
#include "hidsio.h"
|
|
|
|
|
|
#define ICLASS_DEBIT_KEYTYPE ( 0x88 )
|
|
#define ICLASS_CREDIT_KEYTYPE ( 0x18 )
|
|
|
|
#define NUM_CSNS 9
|
|
#define MAC_ITEM_SIZE 24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes
|
|
#define ICLASS_KEYS_MAX 8
|
|
#define ICLASS_AUTH_RETRY 10
|
|
#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_DEFAULT_KEY_DIC "iclass_default_keys.dic"
|
|
#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic"
|
|
|
|
static void print_picopass_info(const picopass_hdr_t *hdr);
|
|
void print_picopass_header(const picopass_hdr_t *hdr);
|
|
|
|
static picopass_hdr_t iclass_last_known_card;
|
|
static void iclass_set_last_known_card(picopass_hdr_t *card) {
|
|
memcpy(&iclass_last_known_card, card, sizeof(picopass_hdr_t));
|
|
}
|
|
|
|
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};
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
|
|
static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][PICOPASS_BLOCK_SIZE] = {
|
|
{ 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 },
|
|
{ 0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90 },
|
|
{ 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87 },
|
|
{ 0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00 },
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
|
|
};
|
|
|
|
static int cmp_uint32(const void *a, const void *b) {
|
|
|
|
const iclass_prekey_t *x = (const iclass_prekey_t *)a;
|
|
const iclass_prekey_t *y = (const iclass_prekey_t *)b;
|
|
|
|
uint32_t mx = bytes_to_num((uint8_t *)x->mac, 4);
|
|
uint32_t my = bytes_to_num((uint8_t *)y->mac, 4);
|
|
|
|
if (mx < my)
|
|
return -1;
|
|
else
|
|
return mx > my;
|
|
}
|
|
|
|
bool check_known_default(uint8_t *csn, uint8_t *epurse, uint8_t *rmac, uint8_t *tmac, uint8_t *key) {
|
|
|
|
iclass_prekey_t *prekey = calloc(ICLASS_KEYS_MAX * 2, sizeof(iclass_prekey_t));
|
|
if (prekey == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return false;
|
|
}
|
|
|
|
uint8_t ccnr[12];
|
|
memcpy(ccnr, epurse, 8);
|
|
memcpy(ccnr + 8, rmac, 4);
|
|
|
|
GenerateMacKeyFrom(csn, ccnr, false, false, (uint8_t *)iClass_Key_Table, ICLASS_KEYS_MAX, prekey);
|
|
GenerateMacKeyFrom(csn, ccnr, false, true, (uint8_t *)iClass_Key_Table, ICLASS_KEYS_MAX, prekey + ICLASS_KEYS_MAX);
|
|
qsort(prekey, ICLASS_KEYS_MAX * 2, sizeof(iclass_prekey_t), cmp_uint32);
|
|
|
|
iclass_prekey_t lookup;
|
|
memcpy(lookup.mac, tmac, 4);
|
|
|
|
// binsearch
|
|
iclass_prekey_t *item = (iclass_prekey_t *) bsearch(&lookup, prekey, ICLASS_KEYS_MAX * 2, sizeof(iclass_prekey_t), cmp_uint32);
|
|
if (item != NULL) {
|
|
memcpy(key, item->key, 8);
|
|
free(prekey);
|
|
return true;
|
|
}
|
|
free(prekey);
|
|
return false;
|
|
}
|
|
|
|
typedef enum {
|
|
None = 0,
|
|
DES,
|
|
RFU,
|
|
TRIPLEDES
|
|
} BLOCK79ENCRYPTION;
|
|
|
|
// 16 bytes key
|
|
static int iclass_load_transport(uint8_t *key, uint8_t n) {
|
|
size_t keylen = 0;
|
|
uint8_t *keyptr = NULL;
|
|
int res = loadFile_safeEx(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen, false);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "Couldn't find any decryption methods");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (keylen != 16) {
|
|
PrintAndLogEx(ERR, "Failed to load transport key from file");
|
|
free(keyptr);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (keylen != n) {
|
|
PrintAndLogEx(ERR, "Array size mismatch");
|
|
free(keyptr);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
memcpy(key, keyptr, n);
|
|
free(keyptr);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void iclass_decrypt_transport(uint8_t *key, uint8_t limit, uint8_t *enc_data, uint8_t *dec_data, BLOCK79ENCRYPTION aa1_encryption) {
|
|
|
|
// tripledes
|
|
mbedtls_des3_context ctx;
|
|
mbedtls_des3_set2key_dec(&ctx, key);
|
|
|
|
bool decrypted_block789 = false;
|
|
for (uint8_t i = 0; i < limit; ++i) {
|
|
|
|
uint16_t idx = i * PICOPASS_BLOCK_SIZE;
|
|
|
|
switch (aa1_encryption) {
|
|
// Right now, only 3DES is supported
|
|
case TRIPLEDES:
|
|
// Decrypt block 7,8,9 if configured.
|
|
if (i > 6 && i <= 9 && memcmp(enc_data + idx, empty, PICOPASS_BLOCK_SIZE) != 0) {
|
|
mbedtls_des3_crypt_ecb(&ctx, enc_data + idx, dec_data + idx);
|
|
decrypted_block789 = true;
|
|
}
|
|
break;
|
|
case DES:
|
|
case RFU:
|
|
case None:
|
|
// Nothing to do for None anyway...
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (decrypted_block789) {
|
|
// Set the 2 last bits of block6 to 0 to mark the data as decrypted
|
|
dec_data[(6 * PICOPASS_BLOCK_SIZE) + 7] &= 0xFC;
|
|
}
|
|
}
|
|
|
|
mbedtls_des3_free(&ctx);
|
|
}
|
|
|
|
static inline uint32_t leadingzeros(uint64_t a) {
|
|
#if defined __GNUC__
|
|
return __builtin_clzll(a);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void iclass_upload_emul(uint8_t *d, uint16_t n, uint16_t offset, uint16_t *bytes_sent) {
|
|
|
|
struct p {
|
|
uint16_t offset;
|
|
uint16_t len;
|
|
uint8_t data[];
|
|
} PACKED;
|
|
|
|
// fast push mode
|
|
g_conn.block_after_ACK = true;
|
|
|
|
//Send to device
|
|
*bytes_sent = 0;
|
|
uint16_t bytes_remaining = n;
|
|
|
|
PrintAndLogEx(INFO, "Uploading to emulator memory");
|
|
PrintAndLogEx(INFO, "." NOLF);
|
|
|
|
while (bytes_remaining > 0) {
|
|
uint32_t bytes_in_packet = MIN(PM3_CMD_DATA_SIZE - 4, bytes_remaining);
|
|
if (bytes_in_packet == bytes_remaining) {
|
|
// Disable fast mode on last packet
|
|
g_conn.block_after_ACK = false;
|
|
}
|
|
|
|
struct p *payload = calloc(4 + bytes_in_packet, sizeof(uint8_t));
|
|
if (payload == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
break;
|
|
}
|
|
|
|
payload->offset = offset + *bytes_sent;
|
|
payload->len = bytes_in_packet;
|
|
memcpy(payload->data, d + *bytes_sent, bytes_in_packet);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)payload, 4 + bytes_in_packet);
|
|
free(payload);
|
|
|
|
bytes_remaining -= bytes_in_packet;
|
|
*bytes_sent += bytes_in_packet;
|
|
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
fflush(stdout);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
static const char *card_types[] = {
|
|
"PicoPass 16K / 16", // 000
|
|
"PicoPass 32K with current book 16K / 16", // 001
|
|
"Unknown Card Type!", // 010
|
|
"Unknown Card Type!", // 011
|
|
"PicoPass 2K", // 100
|
|
"Unknown Card Type!", // 101
|
|
"PicoPass 16K / 2", // 110
|
|
"PicoPass 32K with current book 16K / 2", // 111
|
|
};
|
|
|
|
static uint8_t card_app2_limit[] = {
|
|
0x1f,
|
|
0xff,
|
|
0xff,
|
|
0xff,
|
|
0x1f,
|
|
0xff,
|
|
0xff,
|
|
0xff,
|
|
};
|
|
|
|
static iclass_config_card_item_t iclass_config_options[] = {
|
|
// Byte A8 - LED Operations
|
|
{"(LED) - Led idle (Off) / Led read (Off)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Red) / Led read (Off)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Grn) / Led read (Off)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Amber) / Led read (Off)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Off) / Led read (Red)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Red) / Led read (Red)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Grn) / Led read (Red)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Amber) / Led read (Red)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Off) / Led read (Grn)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Red) / Led read (Grn)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Grn) / Led read (Grn)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xAF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Amber) / Led read (Red)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Off) / Led read (Amber)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Red) / Led read (Amber)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Grn) / Led read (Amber)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(LED) - Led idle (Amber) / Led read (Amber)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA8, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
// Byte A9 - Potentially associated with led blinking / led heartbeat operations?
|
|
// Byte A6 - Potentially associated with beep pitch?
|
|
// Byte A7 - BEEP Operations
|
|
{"(BEEP) - Beep on Read (On)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(BEEP) - Beep on Read (Off)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xA7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
// Byte AC - MIFARE CSN Operations
|
|
{"(MIFARE) - CSN Default Output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(MIFARE) - CSN 32 bit Reverse Output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(MIFARE) - CSN 16 bit Output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(MIFARE) - CSN 34 bit Output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
// Bytes AD, AE, AF, B3 - Keypad Operations + not fully mapped
|
|
{"(KEYPAD Output) - Buffer ONE key (8 bit Dorado)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(KEYPAD Output) - Buffer ONE to FIVE keys (standard 26 bit)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAE, 0x0B, 0xAF, 0xFF, 0xAD, 0x15, 0xB3, 0x03}},
|
|
{"(KEYPAD Output) - Local PIN verify", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAD, 0x6D, 0xB3, 0x03, 0x00, 0x00, 0x00, 0x00}},
|
|
// iClass Elite Key Operations
|
|
{"(ELITE Key) - Set ELITE Key and Enable Dual key (Elite + Standard)", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
|
|
{"(ELITE Key) - Set ELITE Key and ENABLE Keyrolling", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
|
|
{"(ELITE Key) - Set ELITE Key and DISABLE Standard Key", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
|
|
// Erroneous / incorrect reader behaviors (read below)
|
|
// Elite Bugger:
|
|
// Sets block 3 of card 0 presented to the reader to 0, sets block 3 of card 1 presented to the reader to the original value of card 0's block 3
|
|
// Continues setting block 3 of presented cards to block 3 of the previous card the reader scanned
|
|
// This renders cards unreadable and hardly recoverable unless the order of the scanned cards is known.
|
|
{"(ELITE Bugger) - Renders cards unusable.", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
|
|
// Reset Operations
|
|
{"(RESET) - Reset READER to defaults", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(RESET) - Reset ENROLLER to defaults", {0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF}},
|
|
// Reader Master Key Operations
|
|
{"(MASTER Key) - Change Reader Master Key to Custom Key", {0x28, 0xCB, 0x91, 0x9D, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
|
|
{"(MASTER Key) - Restore Reader Master Key to Factory Defaults", {0x28, 0xCB, 0x91, 0x9D, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x5C, 0x91, 0xCF, 0x63, 0x34, 0x23, 0xB9}},
|
|
{"", {0}}, // must be the last item
|
|
};
|
|
|
|
static const iclass_config_card_item_t *get_config_card_item(int idx) {
|
|
if (idx > -1 && idx < ARRAYLEN(iclass_config_options)) {
|
|
return &iclass_config_options[idx];
|
|
}
|
|
return &iclass_config_options[ARRAYLEN(iclass_config_options) - 1];
|
|
}
|
|
|
|
static void print_config_cards(void) {
|
|
PrintAndLogEx(INFO, "---- " _CYAN_("Config cards options") " ------------");
|
|
for (int i = 0; i < ARRAYLEN(iclass_config_options) ; ++i) {
|
|
switch (i) {
|
|
case 0:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- LED Operations ----"));
|
|
break;
|
|
case 16:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- BEEP Operations ----"));
|
|
break;
|
|
case 18:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- Mifare Operations ----"));
|
|
break;
|
|
case 22:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- Keypad Operations ----"));
|
|
break;
|
|
case 25:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- iClass Operations ----"));
|
|
break;
|
|
case 29:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- Reset Operations ----"));
|
|
break;
|
|
case 31:
|
|
PrintAndLogEx(INFO, _YELLOW_("---- iClass Master Key Operations ----"));
|
|
break;
|
|
}
|
|
PrintAndLogEx(INFO, "%2d, %s", i, iclass_config_options[i].desc);
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
static void iclass_encrypt_block_data(uint8_t *blk_data, uint8_t *key) {
|
|
uint8_t encrypted_data[16];
|
|
uint8_t *encrypted = encrypted_data;
|
|
mbedtls_des3_context ctx;
|
|
mbedtls_des3_set2key_enc(&ctx, key);
|
|
mbedtls_des3_crypt_ecb(&ctx, blk_data, encrypted);
|
|
memcpy(blk_data, encrypted, 8);
|
|
mbedtls_des3_free(&ctx);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// tagsim live-update helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Write a single 8-byte block to emulator memory without any console output.
|
|
static void iclass_emul_write_block_silent(uint8_t blk, const uint8_t *data8) {
|
|
struct {
|
|
uint16_t offset;
|
|
uint16_t len;
|
|
uint8_t data[PICOPASS_BLOCK_SIZE];
|
|
} PACKED p;
|
|
p.offset = blk * PICOPASS_BLOCK_SIZE;
|
|
p.len = PICOPASS_BLOCK_SIZE;
|
|
memcpy(p.data, data8, PICOPASS_BLOCK_SIZE);
|
|
SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)&p, sizeof(p));
|
|
}
|
|
|
|
// Write the reload flag to emulator offset 32*8 = 256 (one byte past tag data).
|
|
// The ARM simulation loop consumes this flag on the next ACTALL command.
|
|
static void iclass_emul_set_reload_flag(void) {
|
|
struct {
|
|
uint16_t offset;
|
|
uint16_t len;
|
|
uint8_t data[1];
|
|
} PACKED p;
|
|
p.offset = 32 * PICOPASS_BLOCK_SIZE;
|
|
p.len = 1;
|
|
p.data[0] = 1;
|
|
SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)&p, sizeof(p));
|
|
}
|
|
|
|
// Key codes returned by tagsim_poll_key()
|
|
typedef enum {
|
|
TAGSIM_KEY_NONE = 0,
|
|
TAGSIM_KEY_ABORT,
|
|
TAGSIM_KEY_FC_INC, // arrow up
|
|
TAGSIM_KEY_FC_DEC, // arrow down
|
|
TAGSIM_KEY_CN_INC, // arrow right
|
|
TAGSIM_KEY_CN_DEC, // arrow left
|
|
} tagsim_key_t;
|
|
|
|
#ifdef _WIN32
|
|
|
|
static void tagsim_rawmode_enter(void) {}
|
|
static void tagsim_rawmode_exit(void) {}
|
|
|
|
static tagsim_key_t tagsim_poll_key(void) {
|
|
if (!_kbhit()) return TAGSIM_KEY_NONE;
|
|
int c = _getch();
|
|
if (c == '\r' || c == '\n' || c == 0x1B) return TAGSIM_KEY_ABORT;
|
|
if (c == 0 || c == 0xE0) {
|
|
c = _getch();
|
|
if (c == 72) return TAGSIM_KEY_FC_INC; // up
|
|
if (c == 80) return TAGSIM_KEY_FC_DEC; // down
|
|
if (c == 77) return TAGSIM_KEY_CN_INC; // right
|
|
if (c == 75) return TAGSIM_KEY_CN_DEC; // left
|
|
}
|
|
return TAGSIM_KEY_NONE;
|
|
}
|
|
|
|
#else // POSIX
|
|
|
|
static struct termios tagsim_saved_termios;
|
|
static bool tagsim_rawmode_active = false;
|
|
|
|
static void tagsim_rawmode_enter(void) {
|
|
if (tcgetattr(STDIN_FILENO, &tagsim_saved_termios) < 0) return;
|
|
struct termios raw = tagsim_saved_termios;
|
|
raw.c_lflag &= ~(uint32_t)(ICANON | ECHO);
|
|
raw.c_cc[VMIN] = 0;
|
|
raw.c_cc[VTIME] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
|
tagsim_rawmode_active = true;
|
|
}
|
|
|
|
static void tagsim_rawmode_exit(void) {
|
|
if (tagsim_rawmode_active) {
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &tagsim_saved_termios);
|
|
tagsim_rawmode_active = false;
|
|
}
|
|
}
|
|
|
|
static tagsim_key_t tagsim_poll_key(void) {
|
|
char buf[8] = {0};
|
|
int n = (int)read(STDIN_FILENO, buf, sizeof(buf));
|
|
if (n <= 0) return TAGSIM_KEY_NONE;
|
|
if (n == 1) {
|
|
if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == 0x1B)
|
|
return TAGSIM_KEY_ABORT;
|
|
return TAGSIM_KEY_NONE;
|
|
}
|
|
if (n >= 3 && buf[0] == '\033' && buf[1] == '[') {
|
|
if (buf[2] == 'A') return TAGSIM_KEY_FC_INC;
|
|
if (buf[2] == 'B') return TAGSIM_KEY_FC_DEC;
|
|
if (buf[2] == 'C') return TAGSIM_KEY_CN_INC;
|
|
if (buf[2] == 'D') return TAGSIM_KEY_CN_DEC;
|
|
}
|
|
return TAGSIM_KEY_NONE;
|
|
}
|
|
|
|
#endif // _WIN32
|
|
|
|
|
|
static int generate_config_card(const iclass_config_card_item_t *o, uint8_t *key, bool got_kr, uint8_t *card_key, bool got_eki, bool use_elite, bool got_mk, uint8_t *master_key) {
|
|
|
|
// generated config card header
|
|
picopass_hdr_t configcard;
|
|
memset(&configcard, 0xFF, sizeof(picopass_hdr_t));
|
|
memcpy(configcard.csn, "\x41\x87\x66\x00\xFB\xFF\x12\xE0", 8);
|
|
memcpy(&configcard.conf, "\xFF\xFF\xFF\xFF\xF9\xFF\xFF\xBC", 8);
|
|
memcpy(&configcard.epurse, "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8);
|
|
|
|
if (got_eki) {
|
|
HFiClassCalcDivKey(configcard.csn, card_key, configcard.key_d, use_elite);
|
|
} else {
|
|
// defaulting to AA1 ki 0
|
|
HFiClassCalcDivKey(configcard.csn, iClass_Key_Table[0], configcard.key_d, use_elite);
|
|
}
|
|
|
|
// reference
|
|
picopass_hdr_t *cc = &configcard;
|
|
|
|
// get header from card
|
|
PrintAndLogEx(INFO, "trying to read a card..");
|
|
int res = read_iclass_csn(false, false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
cc = &iclass_last_known_card;
|
|
// calc diversified key for selected card
|
|
if (got_eki) {
|
|
HFiClassCalcDivKey(cc->csn, card_key, cc->key_d, use_elite);
|
|
} else {
|
|
// defaulting to AA1 ki 0
|
|
HFiClassCalcDivKey(cc->csn, iClass_Key_Table[0], cc->key_d, use_elite);
|
|
}
|
|
} else {
|
|
PrintAndLogEx(FAILED, "failed to read a card");
|
|
PrintAndLogEx(INFO, "falling back to default config card");
|
|
}
|
|
PrintAndLogEx(INFO, "Generating "_YELLOW_("%s"), o->desc);
|
|
|
|
// generate dump file
|
|
uint8_t app1_limit = cc->conf.app_limit;
|
|
uint8_t old_limit = app1_limit;
|
|
uint16_t tot_bytes = (app1_limit + 1) * 8;
|
|
|
|
PrintAndLogEx(INFO, " APP1 limit: %u", app1_limit);
|
|
PrintAndLogEx(INFO, "total bytes: %u", tot_bytes);
|
|
// normal size
|
|
uint8_t *data = calloc(1, tot_bytes);
|
|
if (data == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
memcpy(data, cc, sizeof(picopass_hdr_t));
|
|
|
|
print_picopass_header(cc);
|
|
// KEYROLL need to encrypt
|
|
uint8_t key_en[16] = {0};
|
|
uint8_t *keyptr_en = NULL;
|
|
size_t keylen = 0;
|
|
int res_key = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr_en, &keylen);
|
|
if (res_key != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to find iclass_decryptionkey.bin");
|
|
free(data);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (keylen != 16) {
|
|
PrintAndLogEx(ERR, "Failed to load transport key from file");
|
|
free(keyptr_en);
|
|
free(data);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(key_en, keyptr_en, sizeof(key_en));
|
|
free(keyptr_en);
|
|
|
|
// Keyrolling configuration cards are special.
|
|
if (strstr(o->desc, "ELITE") != NULL) {
|
|
|
|
if (got_kr == false) {
|
|
PrintAndLogEx(ERR, "please specify ELITE key!");
|
|
free(data);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (app1_limit < 0x16) {
|
|
// if card wasn't large enough before, adapt to new size
|
|
PrintAndLogEx(WARNING, "Adapting applimit1 for KEY rolling..");
|
|
|
|
app1_limit = 0x16;
|
|
cc->conf.app_limit = 0x16;
|
|
tot_bytes = (app1_limit + 1) * 8;
|
|
|
|
uint8_t *p = realloc(data, tot_bytes);
|
|
if (p == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(data);
|
|
return PM3_EMALLOC;
|
|
}
|
|
data = p;
|
|
}
|
|
|
|
memset(data + sizeof(picopass_hdr_t), 0xFF, tot_bytes - sizeof(picopass_hdr_t));
|
|
|
|
bool old = GetFlushAfterWrite();
|
|
SetFlushAfterWrite(true);
|
|
|
|
PrintAndLogEx(INFO, "Setting up encryption... " NOLF);
|
|
uint8_t ffs[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
if (IsCardHelperPresent(false) != false) {
|
|
if (Encrypt(ffs, ffs) == false) {
|
|
PrintAndLogEx(WARNING, "failed to encrypt FF");
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
}
|
|
} else {
|
|
iclass_encrypt_block_data(ffs, key_en);
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
}
|
|
|
|
// local key copy
|
|
PrintAndLogEx(INFO, "Encrypting local key... " NOLF);
|
|
uint8_t lkey[8];
|
|
memcpy(lkey, key, sizeof(lkey));
|
|
uint8_t enckey1[8];
|
|
if (IsCardHelperPresent(false) != false) {
|
|
if (Encrypt(lkey, enckey1) == false) {
|
|
PrintAndLogEx(WARNING, "failed to encrypt key1");
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
}
|
|
} else {
|
|
iclass_encrypt_block_data(lkey, key_en);
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Copy data... " NOLF);
|
|
memcpy(data, cc, sizeof(picopass_hdr_t));
|
|
memcpy(data + (6 * 8), o->data, sizeof(o->data));
|
|
|
|
// encrypted keyroll key 0D
|
|
if (IsCardHelperPresent(false) != false) {
|
|
memcpy(data + (0x0D * 8), enckey1, sizeof(enckey1));
|
|
} else {
|
|
memcpy(data + (0x0D * 8), lkey, sizeof(enckey1));
|
|
}
|
|
// encrypted 0xFF
|
|
for (uint8_t i = 0x0E; i < 0x13; i++) {
|
|
memcpy(data + (i * 8), ffs, sizeof(ffs));
|
|
}
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
|
|
//Block 13 (This is needed for Rev.C readers!)
|
|
uint8_t block_0x13[PICOPASS_BLOCK_SIZE] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C};
|
|
memcpy(data + (0x13 * 8), block_0x13, sizeof(block_0x13));
|
|
|
|
// encrypted partial keyroll key 14
|
|
PrintAndLogEx(INFO, "Setting encrypted partial key14... " NOLF);
|
|
uint8_t foo[8] = {0x15};
|
|
memcpy(foo + 1, key, 7);
|
|
uint8_t enckey2[8];
|
|
if (IsCardHelperPresent(false) != false) {
|
|
if (Encrypt(foo, enckey2) == false) {
|
|
PrintAndLogEx(WARNING, "failed to encrypt partial 1");
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
memcpy(data + (0x14 * 8), enckey2, sizeof(enckey2));
|
|
}
|
|
} else {
|
|
iclass_encrypt_block_data(foo, key_en);
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
memcpy(data + (0x14 * 8), foo, sizeof(enckey2));
|
|
}
|
|
|
|
// encrypted partial keyroll key 15
|
|
PrintAndLogEx(INFO, "Setting encrypted partial key15... " NOLF);
|
|
memset(foo, 0xFF, sizeof(foo));
|
|
foo[0] = key[7];
|
|
if (IsCardHelperPresent(false) != false) {
|
|
if (Encrypt(foo, enckey2) == false) {
|
|
PrintAndLogEx(WARNING, "failed to encrypt partial 2");
|
|
} else {
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
memcpy(data + (0x15 * 8), enckey2, sizeof(enckey2));
|
|
}
|
|
} else {
|
|
iclass_encrypt_block_data(foo, key_en);
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
memcpy(data + (0x15 * 8), foo, sizeof(enckey2));
|
|
}
|
|
|
|
// encrypted 0xFF
|
|
PrintAndLogEx(INFO, "Setting 0xFF's... " NOLF);
|
|
for (uint16_t i = 0x16; i < (app1_limit + 1); i++) {
|
|
memcpy(data + (i * 8), ffs, sizeof(ffs));
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
|
|
|
|
// revert potential modified app1_limit
|
|
cc->conf.app_limit = old_limit;
|
|
|
|
SetFlushAfterWrite(old);
|
|
} else {
|
|
memcpy(data, cc, sizeof(picopass_hdr_t));
|
|
memcpy(data + (6 * 8), o->data, sizeof(o->data));
|
|
if (strstr(o->desc, "Custom") != NULL) {
|
|
if (got_mk == false) {
|
|
PrintAndLogEx(ERR, "please specify New Master Key!");
|
|
free(data);
|
|
return PM3_EINVARG;
|
|
}
|
|
iclass_encrypt_block_data(master_key, key_en);
|
|
memcpy(data + (0x07 * 8), master_key, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
}
|
|
|
|
//Send to device
|
|
PrintAndLogEx(INFO, "Uploading to device... ");
|
|
uint16_t bytes_sent = 0;
|
|
iclass_upload_emul(data, tot_bytes, 0, &bytes_sent);
|
|
free(data);
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "sent " _YELLOW_("%u") " bytes of data to device emulator memory", bytes_sent);
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass eview") "` to view dump file");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass sim -t 3") "` to start simulating config card");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static uint8_t isset(uint8_t val, uint8_t mask) {
|
|
return (val & mask);
|
|
}
|
|
|
|
static uint8_t notset(uint8_t val, uint8_t mask) {
|
|
return !(val & mask);
|
|
}
|
|
|
|
uint8_t get_pagemap(const picopass_hdr_t *hdr) {
|
|
return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
|
|
}
|
|
|
|
static void fuse_config(const picopass_hdr_t *hdr) {
|
|
|
|
uint16_t otp = (hdr->conf.otp[1] << 8 | hdr->conf.otp[0]);
|
|
|
|
PrintAndLogEx(INFO, " Raw: " _YELLOW_("%s"), sprint_hex((uint8_t *)&hdr->conf, 8));
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") " ( %3u )............. app limit", hdr->conf.app_limit, hdr->conf.app_limit);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%04X") " ( %5u )...... OTP", otp, otp);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") "............ block write lock", hdr->conf.block_writelock);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") "......... chip", hdr->conf.chip_config);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") "...... mem", hdr->conf.mem_config);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") "... EAS", hdr->conf.eas);
|
|
PrintAndLogEx(INFO, " " _YELLOW_("%02X") " fuses", hdr->conf.fuses);
|
|
|
|
uint8_t fuses = hdr->conf.fuses;
|
|
|
|
PrintAndLogEx(INFO, " Fuses:");
|
|
if (isset(fuses, FUSE_FPERS))
|
|
PrintAndLogEx(SUCCESS, " mode......... " _GREEN_("Personalization (programmable)"));
|
|
else
|
|
PrintAndLogEx(SUCCESS, " mode......... " _YELLOW_("Application (locked)"));
|
|
|
|
if (isset(fuses, FUSE_CODING1)) {
|
|
PrintAndLogEx(SUCCESS, " coding...... RFU");
|
|
} else {
|
|
if (isset(fuses, FUSE_CODING0))
|
|
PrintAndLogEx(SUCCESS, " coding....... " _YELLOW_("ISO 14443-2 B / 15693"));
|
|
else
|
|
PrintAndLogEx(SUCCESS, " coding....... " _YELLOW_("ISO 14443-B only"));
|
|
}
|
|
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
switch (pagemap) {
|
|
case 0x0:
|
|
PrintAndLogEx(INFO, " crypt........ No auth possible. Read only if RA is enabled");
|
|
break;
|
|
case 0x1:
|
|
PrintAndLogEx(SUCCESS, " crypt........ Non secured page");
|
|
break;
|
|
case 0x2:
|
|
PrintAndLogEx(INFO, " crypt........ Secured page, keys locked");
|
|
break;
|
|
case 0x03:
|
|
PrintAndLogEx(SUCCESS, " crypt........ Secured page, " _GREEN_("keys not locked"));
|
|
break;
|
|
}
|
|
|
|
if (isset(fuses, FUSE_RA))
|
|
PrintAndLogEx(SUCCESS, " RA........... Read access enabled (non-secure mode)");
|
|
else
|
|
PrintAndLogEx(INFO, " RA........... Read access not enabled");
|
|
|
|
if (notset(fuses, FUSE_FPROD0) && isset(fuses, FUSE_FPROD1)) {
|
|
PrintAndLogEx(INFO, " PROD0/1...... Default production fuses");
|
|
}
|
|
}
|
|
|
|
static void getMemConfig(uint8_t mem_cfg, uint8_t chip_cfg, uint8_t *app_areas, uint8_t *kb, uint8_t *books, uint8_t *pages) {
|
|
// How to determine chip type
|
|
|
|
// mem-bit 7 = 16K
|
|
// mem-bit 5 = Book
|
|
// mem-bit 4 = 2K
|
|
// chip-bit 4 = Multi App
|
|
*books = 1;
|
|
*pages = 1;
|
|
|
|
uint8_t k16 = isset(mem_cfg, 0x80);
|
|
//uint8_t k2 = isset(mem_cfg, 0x10);
|
|
uint8_t book = isset(mem_cfg, 0x20);
|
|
|
|
if (isset(chip_cfg, 0x10) && !k16 && !book) {
|
|
*kb = 2;
|
|
*app_areas = 2;
|
|
} else if (isset(chip_cfg, 0x10) && k16 && !book) {
|
|
*kb = 16;
|
|
*app_areas = 2;
|
|
} else if (notset(chip_cfg, 0x10) && !k16 && !book) {
|
|
*kb = 16;
|
|
*app_areas = 16;
|
|
*pages = 8;
|
|
} else if (isset(chip_cfg, 0x10) && k16 && book) {
|
|
*kb = 32;
|
|
*app_areas = 3;
|
|
*books = 2;
|
|
} else if (notset(chip_cfg, 0x10) && !k16 && book) {
|
|
*kb = 32;
|
|
*app_areas = 17;
|
|
*pages = 8;
|
|
*books = 2;
|
|
} else {
|
|
*kb = 32;
|
|
*app_areas = 2;
|
|
}
|
|
}
|
|
|
|
static uint8_t get_mem_config(const picopass_hdr_t *hdr) {
|
|
// three configuration bits that decides sizes
|
|
uint8_t type = (hdr->conf.chip_config & 0x10) >> 2;
|
|
// 16K bit 0 == 1==
|
|
type |= (hdr->conf.mem_config & 0x80) >> 6;
|
|
// BOOK bit 0 == 1==
|
|
type |= (hdr->conf.mem_config & 0x20) >> 5;
|
|
// 2K
|
|
//type |= (hdr->conf.mem_config & 0x10) >> 5;
|
|
return type;
|
|
}
|
|
|
|
static void mem_app_config(const picopass_hdr_t *hdr) {
|
|
uint8_t mem = hdr->conf.mem_config;
|
|
uint8_t chip = hdr->conf.chip_config;
|
|
uint8_t kb = 2;
|
|
uint8_t app_areas = 2;
|
|
uint8_t books = 1;
|
|
uint8_t pages = 1;
|
|
|
|
getMemConfig(mem, chip, &app_areas, &kb, &books, &pages);
|
|
|
|
uint8_t type = get_mem_config(hdr);
|
|
uint8_t app1_limit = hdr->conf.app_limit - 5; // minus header blocks
|
|
uint8_t app2_limit = card_app2_limit[type];
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
|
|
PrintAndLogEx(INFO, "------------------------ " _CYAN_("Memory") " -------------------------");
|
|
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(INFO, " %u KBits ( " _YELLOW_("%u") " bytes )", kb, app2_limit * 8);
|
|
PrintAndLogEx(INFO, " Tag has not App Areas");
|
|
return;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, " %u KBits/%u App Areas ( " _YELLOW_("%u") " bytes )"
|
|
, kb
|
|
, app_areas
|
|
, ((app2_limit + 1) * 8) * books * pages);
|
|
|
|
PrintAndLogEx(INFO, " %u books / %u pages"
|
|
, books
|
|
, pages
|
|
);
|
|
PrintAndLogEx(INFO, " First book / first page configuration");
|
|
PrintAndLogEx(INFO, " Config | 0 - 5 ( 0x00 - 0x05 ) - 6 blocks ");
|
|
PrintAndLogEx(INFO, " AA1 | 6 - %2d ( 0x06 - 0x%02X ) - %u blocks", app1_limit + 5, app1_limit + 5, app1_limit);
|
|
if (app1_limit + 5 < app2_limit) {
|
|
PrintAndLogEx(INFO, " AA2 | %2d - %2d ( 0x%02X - 0x%02X ) - %u blocks", app1_limit + 5 + 1, app2_limit, app1_limit + 5 + 1, app2_limit, app2_limit - app1_limit);
|
|
}
|
|
/*
|
|
[=] 32 KBits/3 App Areas ( 2048 bytes )
|
|
[=] AA1 blocks 250 { 0x06 - 0xFF (06 - 255) }
|
|
[=] AA2 blocks 5 { 0x100 - 0xFF (256 - 255) }
|
|
*/
|
|
|
|
PrintAndLogEx(INFO, "----------------------- " _CYAN_("KeyAccess") " -----------------------");
|
|
PrintAndLogEx(INFO, " * Kd, Debit key, AA1 Kc, Credit key, AA2 *");
|
|
uint8_t keyAccess = isset(mem, 0x01);
|
|
if (keyAccess) {
|
|
PrintAndLogEx(INFO, " Read AA1..... debit");
|
|
PrintAndLogEx(INFO, " Write AA1.... debit");
|
|
PrintAndLogEx(INFO, " Read AA2..... credit");
|
|
PrintAndLogEx(INFO, " Write AA2.... credit");
|
|
PrintAndLogEx(INFO, " Debit........ debit or credit");
|
|
PrintAndLogEx(INFO, " Credit....... credit");
|
|
} else {
|
|
PrintAndLogEx(INFO, " Read AA1..... debit or credit");
|
|
PrintAndLogEx(INFO, " Write AA1.... credit");
|
|
PrintAndLogEx(INFO, " Read AA2..... debit or credit");
|
|
PrintAndLogEx(INFO, " Write AA2.... credit");
|
|
PrintAndLogEx(INFO, " Debit........ debit or credit");
|
|
PrintAndLogEx(INFO, " Credit....... credit");
|
|
}
|
|
}
|
|
|
|
void print_picopass_info(const picopass_hdr_t *hdr) {
|
|
PrintAndLogEx(INFO, "------------------- " _CYAN_("Card configuration") " ------------------");
|
|
fuse_config(hdr);
|
|
mem_app_config(hdr);
|
|
}
|
|
|
|
void print_picopass_header(const picopass_hdr_t *hdr) {
|
|
PrintAndLogEx(INFO, "-------------------------- " _CYAN_("Card") " -------------------------");
|
|
PrintAndLogEx(SUCCESS, " CSN... " _GREEN_("%s") " uid", sprint_hex(hdr->csn, sizeof(hdr->csn)));
|
|
PrintAndLogEx(SUCCESS, " Config... %s card configuration", sprint_hex((uint8_t *)&hdr->conf, sizeof(hdr->conf)));
|
|
PrintAndLogEx(SUCCESS, "E-purse... %s card challenge, CC", sprint_hex(hdr->epurse, sizeof(hdr->epurse)));
|
|
|
|
if (memcmp(hdr->key_d, zeros, sizeof(zeros)) && memcmp(hdr->key_d, empty, sizeof(empty))) {
|
|
PrintAndLogEx(SUCCESS, " Kd... " _YELLOW_("%s") " debit key", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Kd... -- -- -- -- -- -- -- -- debit key ( hidden )");
|
|
}
|
|
|
|
if (memcmp(hdr->key_c, zeros, sizeof(zeros)) && memcmp(hdr->key_c, empty, sizeof(empty))) {
|
|
PrintAndLogEx(SUCCESS, " Kc... " _YELLOW_("%s") " credit key", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Kc... -- -- -- -- -- -- -- -- credit key ( hidden )");
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " AIA... %s application issuer area", sprint_hex(hdr->app_issuer_area, sizeof(hdr->app_issuer_area)));
|
|
}
|
|
|
|
static int CmdHFiClassList(const char *Cmd) {
|
|
return CmdTraceListAlias(Cmd, "hf iclass", "iclass -c");
|
|
}
|
|
|
|
static int CmdHFiClassSniff(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sniff",
|
|
"Sniff the communication between reader and tag",
|
|
"hf iclass sniff\n"
|
|
"hf iclass sniff -j --> jam e-purse updates\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("j", "jam", "Jam (prevent) e-purse updates"),
|
|
arg_param_end
|
|
};
|
|
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool jam_epurse_update = arg_get_lit(ctx, 1);
|
|
CLIParserFree(ctx);
|
|
|
|
if (jam_epurse_update) {
|
|
PrintAndLogEx(INFO, "Sniff with jam of iCLASS e-purse updates...");
|
|
}
|
|
|
|
struct {
|
|
uint8_t jam_search_len;
|
|
uint8_t jam_search_string[2];
|
|
} PACKED payload;
|
|
|
|
memset(&payload, 0, sizeof(payload));
|
|
|
|
if (jam_epurse_update) {
|
|
const uint8_t update_epurse_sequence[2] = {0x87, 0x02};
|
|
payload.jam_search_len = sizeof(update_epurse_sequence);
|
|
memcpy(payload.jam_search_string, update_epurse_sequence, sizeof(payload.jam_search_string));
|
|
}
|
|
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_SNIFF, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
WaitForResponse(CMD_HF_ICLASS_SNIFF, &resp);
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass list") "` to view captured tracelog");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace save -f hf_iclass_mytrace") "` to save tracelog for later analysing");
|
|
if (jam_epurse_update) {
|
|
PrintAndLogEx(HINT, "Hint: Verify if the jam worked by comparing value in trace and block 2");
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassSim(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sim",
|
|
"Simulate a iCLASS legacy/standard tag",
|
|
"hf iclass sim -t 0 --csn 031FEC8AF7FF12E0 --> simulate with specified CSN\n"
|
|
"hf iclass sim -t 1 --> simulate with default CSN\n"
|
|
"hf iclass sim -t 2 --> execute loclass attack online part\n"
|
|
"hf iclass sim -t 3 --> simulate full iCLASS 2k tag\n"
|
|
"hf iclass sim -t 4 --> Reader-attack, adapted for KeyRoll mode, gather reader responses to extract elite key\n"
|
|
"hf iclass sim -t 6 --> simulate full iCLASS 2k tag that doesn't respond to r/w requests to the last SIO block\n"
|
|
"hf iclass sim -t 7 --> simulate full iCLASS 2k tag that doesn't XOR or respond to r/w requests on block 3");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1("t", "type", "<0-4> ", "Simulation type to use"),
|
|
arg_str0(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes to use with sim type 0"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int sim_type = arg_get_int_def(ctx, 1, 3);
|
|
|
|
int csn_len = 0;
|
|
uint8_t csn[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, csn, &csn_len);
|
|
|
|
if (sim_type == 0 && csn_len > 0) {
|
|
if (csn_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
PrintAndLogEx(INFO, " simtype: %02x CSN: %s", sim_type, sprint_hex(csn, 8));
|
|
} else if (sim_type == 0 && csn_len == 0) {
|
|
PrintAndLogEx(ERR, "Simtype 0 requires CSN argument (--csn)");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (sim_type > 4 && sim_type != 6 && sim_type != 7) {
|
|
PrintAndLogEx(ERR, "Undefined simtype %d", sim_type);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// remember to change the define NUM_CSNS to match.
|
|
|
|
// pre-defined 9 CSN by iceman
|
|
uint8_t csns[NUM_CSNS * PICOPASS_BLOCK_SIZE] = {
|
|
0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0,
|
|
0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0
|
|
//0x04, 0x08, 0x9F, 0x78, 0x6E, 0xFF, 0x12, 0xE0
|
|
};
|
|
|
|
/* DUMPFILE FORMAT:
|
|
*
|
|
* <8-byte CSN><8-byte CC><4 byte NR><4 byte MAC>....
|
|
* So, it should wind up as
|
|
* 8 * 24 bytes.
|
|
*
|
|
* The returndata from the pm3 is on the following format
|
|
* <4 byte NR><4 byte MAC>
|
|
* CC are all zeroes, CSN is the same as was sent in
|
|
**/
|
|
uint8_t tries = 0;
|
|
|
|
switch (sim_type) {
|
|
|
|
case ICLASS_SIM_MODE_READER_ATTACK: {
|
|
PrintAndLogEx(INFO, "Starting iCLASS sim 2 attack (elite mode)");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, NUM_CSNS * PICOPASS_BLOCK_SIZE);
|
|
|
|
while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
|
|
tries++;
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
if (tries > 20) {
|
|
PrintAndLogEx(WARNING, "\ntimeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
}
|
|
uint8_t num_mac = resp.oldarg[1];
|
|
bool success = (NUM_CSNS == num_mac);
|
|
PrintAndLogEx((success) ? SUCCESS : WARNING, "[%c] %d out of %d MAC obtained [%s]", (success) ? '+' : '!', num_mac, NUM_CSNS, (success) ? "OK" : "FAIL");
|
|
|
|
if (num_mac == 0)
|
|
break;
|
|
|
|
size_t datalen = NUM_CSNS * MAC_ITEM_SIZE;
|
|
uint8_t *dump = calloc(datalen, sizeof(uint8_t));
|
|
if (dump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
memset(dump, 0, datalen);//<-- Need zeroes for the EPURSE - field (official)
|
|
|
|
uint8_t i = 0;
|
|
for (i = 0 ; i < NUM_CSNS ; i++) {
|
|
//copy CSN
|
|
memcpy(dump + (i * MAC_ITEM_SIZE), csns + i * 8, 8);
|
|
//copy epurse
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 8, resp.data.asBytes + i * 16, 8);
|
|
// NR_MAC (eight bytes from the response) ( 8b csn + 8b epurse == 16)
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 16, resp.data.asBytes + i * 16 + 8, 8);
|
|
}
|
|
/** Now, save to dumpfile **/
|
|
saveFile("iclass_mac_attack", ".bin", dump, datalen);
|
|
free(dump);
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass loclass -f iclass_mac_attack.bin") "` to recover elite key");
|
|
break;
|
|
}
|
|
case ICLASS_SIM_MODE_READER_ATTACK_KEYROLL: {
|
|
// reader in key roll mode, when it has two keys it alternates when trying to verify.
|
|
PrintAndLogEx(INFO, "Starting iCLASS sim 4 attack (elite mode, reader in key roll mode)");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, NUM_CSNS * PICOPASS_BLOCK_SIZE);
|
|
|
|
while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
|
|
tries++;
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
if (tries > 20) {
|
|
PrintAndLogEx(WARNING, "\ntimeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
}
|
|
uint8_t num_mac = resp.oldarg[1];
|
|
bool success = ((NUM_CSNS * 2) == num_mac);
|
|
PrintAndLogEx((success) ? SUCCESS : WARNING, "[%c] %d out of %d MAC obtained [%s]", (success) ? '+' : '!', num_mac, NUM_CSNS * 2, (success) ? "OK" : "FAIL");
|
|
|
|
if (num_mac == 0)
|
|
break;
|
|
|
|
size_t datalen = NUM_CSNS * MAC_ITEM_SIZE;
|
|
uint8_t *dump = calloc(datalen, sizeof(uint8_t));
|
|
if (dump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
//KEYROLL 1
|
|
//Need zeroes for the CC-field
|
|
memset(dump, 0, datalen);
|
|
for (uint8_t i = 0; i < NUM_CSNS ; i++) {
|
|
// copy CSN
|
|
memcpy(dump + (i * MAC_ITEM_SIZE), csns + i * 8, 8); //CSN
|
|
// copy EPURSE
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 8, resp.data.asBytes + i * 16, 8);
|
|
// copy NR_MAC (eight bytes from the response) ( 8b csn + 8b epurse == 16)
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 16, resp.data.asBytes + i * 16 + 8, 8);
|
|
}
|
|
saveFile("iclass_mac_attack_keyroll_A", ".bin", dump, datalen);
|
|
|
|
//KEYROLL 2
|
|
memset(dump, 0, datalen);
|
|
for (uint8_t i = 0; i < NUM_CSNS; i++) {
|
|
uint8_t resp_index = (i + NUM_CSNS) * 16;
|
|
// Copy CSN
|
|
memcpy(dump + (i * MAC_ITEM_SIZE), csns + i * 8, 8);
|
|
// copy EPURSE
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 8, resp.data.asBytes + resp_index, 8);
|
|
// copy NR_MAC (eight bytes from the response) ( 8b csn + 8 epurse == 16)
|
|
memcpy(dump + (i * MAC_ITEM_SIZE) + 16, resp.data.asBytes + resp_index + 8, 8);
|
|
resp_index++;
|
|
}
|
|
saveFile("iclass_mac_attack_keyroll_B", ".bin", dump, datalen);
|
|
free(dump);
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass loclass -f iclass_mac_attack_keyroll_A.bin") "` to recover elite key");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass loclass -f iclass_mac_attack_keyroll_B.bin") "` to recover elite key");
|
|
break;
|
|
}
|
|
case ICLASS_SIM_MODE_CSN:
|
|
case ICLASS_SIM_MODE_CSN_DEFAULT:
|
|
case ICLASS_SIM_MODE_FULL:
|
|
case ICLASS_SIM_MODE_FULL_GLITCH:
|
|
case ICLASS_SIM_MODE_FULL_GLITCH_KEY:
|
|
default: {
|
|
PrintAndLogEx(INFO, "Starting iCLASS simulation");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("`pm3 button`") " to abort");
|
|
uint8_t numberOfCSNs = 0;
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, numberOfCSNs, 1, csn, 8);
|
|
|
|
if (sim_type == ICLASS_SIM_MODE_FULL || sim_type == ICLASS_SIM_MODE_FULL_GLITCH || sim_type == ICLASS_SIM_MODE_FULL_GLITCH_KEY)
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass esave -h") "` to save the emulator memory to file");
|
|
break;
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassTagSim(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass tagsim",
|
|
"Build a complete iCLASS 2K tag dump from facility code, card number, and keys,\n"
|
|
"upload it to emulator memory, and start a full simulation.\n"
|
|
"Use either --bin or --wiegand/--fc/--cn to specify the credential.\n"
|
|
"Provide a debit key via --kd or --ki. If no transport key is given,\n"
|
|
"the tool tries to load " ICLASS_DECRYPTION_BIN ".",
|
|
"hf iclass tagsim --fc 101 --cn 1337\n"
|
|
"hf iclass tagsim -w H10301 --fc 101 --cn 1337 --ki 0\n"
|
|
"hf iclass tagsim -w H10301 --fc 101 --cn 1337 --kd 0102030405060708 --elite\n"
|
|
"hf iclass tagsim --bin 10001111100000001010100011 --ki 0\n"
|
|
"hf iclass tagsim -w H10301 --fc 101 --cn 1337 --ki 0 --enckey 00000000000000000000000000000000\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("w", "wiegand", "<format>", "Wiegand format (default H10301), see `wiegand list`"),
|
|
arg_u64_0(NULL, "fc", "<dec>", "Facility code"),
|
|
arg_u64_0(NULL, "cn", "<dec>", "Card number"),
|
|
arg_u64_0(NULL, "issue", "<dec>", "Issue level"),
|
|
arg_str0(NULL, "bin", "<bin>", "Binary wiegand string (alternative to --wiegand/--fc/--cn)"),
|
|
arg_str0(NULL, "kd", "<hex>", "Debit master key, 8 hex bytes"),
|
|
arg_str0(NULL, "kc", "<hex>", "Credit master key, 8 hex bytes (defaults to kd if omitted)"),
|
|
arg_int0(NULL, "ki", "<dec>", "Debit key index from key manager (replaces --kd)"),
|
|
arg_int0(NULL, "ci", "<dec>", "Credit key index from key manager (replaces --kc)"),
|
|
arg_lit0(NULL, "elite", "Elite key diversification"),
|
|
arg_lit0(NULL, "raw", "Keys are already diversified, skip diversification"),
|
|
arg_str0(NULL, "csn", "<hex>", "Custom CSN, 8 hex bytes (auto-generated if omitted)"),
|
|
arg_str0(NULL, "enckey", "<hex>", "3DES transport key, 16 hex bytes"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
// --- wiegand format
|
|
char format[16] = {0};
|
|
int format_len = 0;
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)format, sizeof(format), &format_len);
|
|
if (format_len == 0)
|
|
strncpy(format, "H10301", sizeof(format) - 1);
|
|
|
|
wiegand_card_t card;
|
|
memset(&card, 0, sizeof(wiegand_card_t));
|
|
card.FacilityCode = arg_get_u32_def(ctx, 2, 0);
|
|
card.CardNumber = arg_get_u32_def(ctx, 3, 0);
|
|
card.IssueLevel = arg_get_u32_def(ctx, 4, 0);
|
|
|
|
// --- binary string
|
|
uint8_t bin[65] = {0};
|
|
int bin_len = sizeof(bin) - 1;
|
|
CLIGetStrWithReturn(ctx, 5, bin, &bin_len);
|
|
|
|
// --- debit key
|
|
int kd_len = 0;
|
|
uint8_t kd_master[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 6, kd_master, &kd_len);
|
|
|
|
// --- credit key
|
|
int kc_len = 0;
|
|
uint8_t kc_master[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 7, kc_master, &kc_len);
|
|
|
|
// --- key indices
|
|
int key_nr = arg_get_int_def(ctx, 8, -1);
|
|
int credit_nr = arg_get_int_def(ctx, 9, -1);
|
|
|
|
// --- flags
|
|
bool elite = arg_get_lit(ctx, 10);
|
|
bool rawkey = arg_get_lit(ctx, 11);
|
|
|
|
// --- custom CSN
|
|
int csn_len = 0;
|
|
uint8_t csn[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 12, csn, &csn_len);
|
|
bool have_custom_csn = (csn_len == 8);
|
|
|
|
// --- transport key
|
|
int enc_key_len = 0;
|
|
uint8_t enc_key[16] = {0};
|
|
uint8_t *enckeyptr = NULL;
|
|
bool have_enc_key = false;
|
|
CLIGetHexWithReturn(ctx, 13, enc_key, &enc_key_len);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
// --- validation
|
|
if ((rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Cannot combine --elite and --raw");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (csn_len > 0 && csn_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN must be exactly 8 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (bin_len > 64) {
|
|
PrintAndLogEx(ERR, "Binary wiegand string must be at most 64 bits");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (bin_len == 0 && card.FacilityCode == 0 && card.CardNumber == 0) {
|
|
PrintAndLogEx(ERR, "Must provide either --cn/--fc or --bin");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// --- resolve debit key: --kd takes priority, then --ki, then default ki 0
|
|
if (kd_len == 0) {
|
|
if (key_nr < 0)
|
|
key_nr = 0;
|
|
if (key_nr >= ICLASS_KEYS_MAX) {
|
|
PrintAndLogEx(ERR, "Debit key index is out of range (max %d)", ICLASS_KEYS_MAX - 1);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(kd_master, iClass_Key_Table[key_nr], 8);
|
|
kd_len = 8;
|
|
PrintAndLogEx(SUCCESS, "Using debit key[%d] " _GREEN_("%s"), key_nr, sprint_hex(kd_master, 8));
|
|
} else if (kd_len != 8) {
|
|
PrintAndLogEx(ERR, "Debit key must be exactly 8 hex bytes");
|
|
return PM3_EINVARG;
|
|
} else if (key_nr >= 0) {
|
|
PrintAndLogEx(SUCCESS, "Using debit key[%d] " _GREEN_("%s"), key_nr, sprint_hex(kd_master, 8));
|
|
}
|
|
|
|
// --- resolve credit key: --kc takes priority, then --ci, then default ci 1
|
|
if (kc_len == 0) {
|
|
if (credit_nr < 0)
|
|
credit_nr = 1;
|
|
if (credit_nr >= ICLASS_KEYS_MAX) {
|
|
PrintAndLogEx(ERR, "Credit key index is out of range (max %d)", ICLASS_KEYS_MAX - 1);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(kc_master, iClass_Key_Table[credit_nr], 8);
|
|
kc_len = 8;
|
|
PrintAndLogEx(SUCCESS, "Using credit key[%d] " _GREEN_("%s"), credit_nr, sprint_hex(kc_master, 8));
|
|
} else if (kc_len != 8) {
|
|
PrintAndLogEx(ERR, "Credit key must be exactly 8 hex bytes");
|
|
return PM3_EINVARG;
|
|
} else if (credit_nr >= 0) {
|
|
PrintAndLogEx(SUCCESS, "Using credit key[%d] " _GREEN_("%s"), credit_nr, sprint_hex(kc_master, 8));
|
|
}
|
|
|
|
// --- resolve transport key
|
|
if (enc_key_len > 0) {
|
|
if (enc_key_len != 16) {
|
|
PrintAndLogEx(ERR, "Transport key must be 16 hex bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
have_enc_key = true;
|
|
}
|
|
|
|
if (have_enc_key == false) {
|
|
// try smart-card helper first, then fall back to file
|
|
bool use_sc = IsCardHelperPresent(false);
|
|
if (use_sc == false) {
|
|
size_t keylen = 0;
|
|
int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&enckeyptr, &keylen);
|
|
if (res == PM3_SUCCESS && keylen == 16) {
|
|
memcpy(enc_key, enckeyptr, 16);
|
|
free(enckeyptr);
|
|
have_enc_key = true;
|
|
} else {
|
|
if (enckeyptr != NULL)
|
|
free(enckeyptr);
|
|
PrintAndLogEx(WARNING, "No transport key found - credential blocks will be written unencrypted");
|
|
}
|
|
} else {
|
|
have_enc_key = true; // will use Encrypt() via smart card
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// Build the 256-byte (32-block) dump in memory
|
|
// ---------------------------------------------------------------
|
|
uint8_t dump[32 * PICOPASS_BLOCK_SIZE];
|
|
memset(dump, 0, sizeof(dump));
|
|
|
|
// Block 0: CSN — auto-generate from FC/CN if not provided
|
|
if (have_custom_csn) {
|
|
memcpy(dump, csn, 8);
|
|
} else {
|
|
uint32_t fc = card.FacilityCode;
|
|
uint32_t cn = card.CardNumber;
|
|
dump[0] = (uint8_t)((fc ^ (cn >> 8)) ^ 0xA3);
|
|
dump[1] = (uint8_t)((fc >> 4) ^ (cn & 0xFF) ^ 0x5C);
|
|
dump[2] = (uint8_t)((cn >> 16) ^ fc ^ 0x7F);
|
|
dump[3] = (uint8_t)((cn >> 8) ^ (fc << 3) ^ 0xE9);
|
|
dump[4] = 0xF7;
|
|
dump[5] = 0xFF;
|
|
dump[6] = 0x12;
|
|
dump[7] = 0xE0;
|
|
}
|
|
memcpy(csn, dump, 8);
|
|
|
|
// Block 1: Config — standard 2K config
|
|
const uint8_t config_block[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
|
|
memcpy(dump + 1 * PICOPASS_BLOCK_SIZE, config_block, 8);
|
|
|
|
// Block 2: Epurse — all 0xFF
|
|
memset(dump + 2 * PICOPASS_BLOCK_SIZE, 0xFF, 8);
|
|
|
|
// Block 3: KD (diversified debit key)
|
|
uint8_t div_kd[8] = {0};
|
|
if (rawkey) {
|
|
memcpy(div_kd, kd_master, 8);
|
|
} else {
|
|
HFiClassCalcDivKey(csn, kd_master, div_kd, elite);
|
|
}
|
|
memcpy(dump + 3 * PICOPASS_BLOCK_SIZE, div_kd, 8);
|
|
|
|
// Block 4: KC (diversified credit key)
|
|
uint8_t div_kc[8] = {0};
|
|
if (rawkey) {
|
|
memcpy(div_kc, kc_master, 8);
|
|
} else {
|
|
HFiClassCalcDivKey(csn, kc_master, div_kc, elite);
|
|
}
|
|
memcpy(dump + 4 * PICOPASS_BLOCK_SIZE, div_kc, 8);
|
|
|
|
// Block 5: AIA — all 0xFF
|
|
memset(dump + 5 * PICOPASS_BLOCK_SIZE, 0xFF, 8);
|
|
|
|
// Blocks 6-9: Credential (app header + wiegand data)
|
|
uint8_t credential[32] = {
|
|
0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17, // block 6: app header
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // block 7: wiegand data
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // block 8: padding
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // block 9: padding
|
|
};
|
|
|
|
if (bin_len > 0) {
|
|
// raw binary string path
|
|
uint8_t data[8];
|
|
memset(data, 0, sizeof(data));
|
|
BitstreamOut_t bout = {data, 0, 0};
|
|
for (int i = 0; i < 64 - bin_len - 1; i++)
|
|
pushBit(&bout, 0);
|
|
pushBit(&bout, 1); // sentinel bit
|
|
for (int i = 0; i < bin_len; i++) {
|
|
char c = (char)bin[i];
|
|
if (c == '1') pushBit(&bout, 1);
|
|
else if (c == '0') pushBit(&bout, 0);
|
|
}
|
|
memcpy(credential + 8, data, 8);
|
|
} else {
|
|
// wiegand format path
|
|
wiegand_message_t packed;
|
|
memset(&packed, 0, sizeof(wiegand_message_t));
|
|
|
|
int format_idx = HIDFindCardFormat(format);
|
|
if (format_idx == -1) {
|
|
PrintAndLogEx(WARNING, "Unknown wiegand format: " _YELLOW_("%s"), format);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (HIDPack(format_idx, &card, &packed, false) == false) {
|
|
PrintAndLogEx(WARNING, "Card data could not be encoded in the selected format");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
packed.Length++;
|
|
set_bit_by_position(&packed, true, 0);
|
|
|
|
#ifdef HOST_LITTLE_ENDIAN
|
|
packed.Mid = BSWAP_32(packed.Mid);
|
|
packed.Bot = BSWAP_32(packed.Bot);
|
|
#endif
|
|
memcpy(credential + 8, &packed.Mid, sizeof(packed.Mid));
|
|
memcpy(credential + 12, &packed.Bot, sizeof(packed.Bot));
|
|
}
|
|
|
|
// Capture smart-card helper state before starting simulation (can't query mid-sim)
|
|
bool use_sc = have_enc_key ? IsCardHelperPresent(false) : false;
|
|
|
|
// Encrypt credential blocks 7, 8, 9
|
|
if (have_enc_key) {
|
|
if (use_sc) {
|
|
Encrypt(credential + 8, credential + 8);
|
|
Encrypt(credential + 16, credential + 16);
|
|
Encrypt(credential + 24, credential + 24);
|
|
} else {
|
|
iclass_encrypt_block_data(credential + 8, enc_key);
|
|
iclass_encrypt_block_data(credential + 16, enc_key);
|
|
iclass_encrypt_block_data(credential + 24, enc_key);
|
|
}
|
|
}
|
|
|
|
memcpy(dump + 6 * PICOPASS_BLOCK_SIZE, credential, sizeof(credential));
|
|
|
|
// --- print summary
|
|
PrintAndLogEx(INFO, "CSN......... " _YELLOW_("%s"), sprint_hex(dump, 8));
|
|
PrintAndLogEx(INFO, "Config...... " _YELLOW_("%s"), sprint_hex(dump + 1 * PICOPASS_BLOCK_SIZE, 8));
|
|
PrintAndLogEx(INFO, "Epurse...... " _YELLOW_("%s"), sprint_hex(dump + 2 * PICOPASS_BLOCK_SIZE, 8));
|
|
PrintAndLogEx(INFO, "KD (div).... " _YELLOW_("%s"), sprint_hex(div_kd, 8));
|
|
PrintAndLogEx(INFO, "KC (div).... " _YELLOW_("%s"), sprint_hex(div_kc, 8));
|
|
PrintAndLogEx(INFO, "Block 6..... " _YELLOW_("%s"), sprint_hex(dump + 6 * PICOPASS_BLOCK_SIZE, 8));
|
|
PrintAndLogEx(INFO, "Block 7..... " _YELLOW_("%s"), sprint_hex(dump + 7 * PICOPASS_BLOCK_SIZE, 8));
|
|
|
|
// --- upload to emulator memory
|
|
if (g_session.pm3_present == false) {
|
|
PrintAndLogEx(ERR, "Device offline");
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
uint16_t bytes_sent = 0;
|
|
iclass_upload_emul(dump, sizeof(dump), 0, &bytes_sent);
|
|
PrintAndLogEx(SUCCESS, "Uploaded " _YELLOW_("%u") " bytes to emulator memory", bytes_sent);
|
|
|
|
// --- start simulation
|
|
PrintAndLogEx(INFO, "Starting iCLASS full simulation");
|
|
if (bin_len == 0) {
|
|
PrintAndLogEx(INFO, _GREEN_("Arrow keys") ": "_CYAN_("up/down")" = FC+/- "_CYAN_("right/left")" = CN+/- | " _GREEN_("Enter") " or " _GREEN_("`pm3 button`") " to stop");
|
|
PrintAndLogEx(INFO, "FC: " _YELLOW_("%u") " CN: " _YELLOW_("%u") " CSN: " _YELLOW_("%s"),
|
|
card.FacilityCode, card.CardNumber, sprint_hex(csn, 8));
|
|
} else {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("`pm3 button`") " to abort");
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, ICLASS_SIM_MODE_FULL_LIVE, 0, 1, csn, 8);
|
|
|
|
// --- live FC/CN navigation (wiegand mode only; binary mode has no FC/CN to adjust)
|
|
if (bin_len == 0) {
|
|
int format_idx = HIDFindCardFormat(format);
|
|
|
|
tagsim_rawmode_enter();
|
|
PacketResponseNG resp;
|
|
bool running = true;
|
|
bool arm_ended = false; // true when ARM sent its own CMD_ACK (e.g. button press)
|
|
|
|
while (running) {
|
|
// A non-zero-timeout poll lets us detect when the ARM ends the sim
|
|
if (WaitForResponseTimeout(CMD_ACK, &resp, 100)) {
|
|
arm_ended = true;
|
|
running = false;
|
|
break;
|
|
}
|
|
|
|
tagsim_key_t k = tagsim_poll_key();
|
|
if (k == TAGSIM_KEY_ABORT) { running = false; break; }
|
|
if (k == TAGSIM_KEY_NONE) { continue; }
|
|
|
|
switch (k) {
|
|
case TAGSIM_KEY_FC_INC:
|
|
card.FacilityCode++;
|
|
break;
|
|
case TAGSIM_KEY_FC_DEC:
|
|
card.FacilityCode--;
|
|
break;
|
|
case TAGSIM_KEY_CN_INC:
|
|
card.CardNumber++;
|
|
break;
|
|
case TAGSIM_KEY_CN_DEC:
|
|
card.CardNumber--;
|
|
break;
|
|
case TAGSIM_KEY_ABORT:
|
|
running = false;
|
|
break;
|
|
case TAGSIM_KEY_NONE:
|
|
break;
|
|
}
|
|
|
|
// Rebuild CSN deterministically from new FC/CN
|
|
{
|
|
uint32_t fc = card.FacilityCode;
|
|
uint32_t cn = card.CardNumber;
|
|
csn[0] = (uint8_t)((fc ^ (cn >> 8)) ^ 0xA3);
|
|
csn[1] = (uint8_t)((fc >> 4) ^ (cn & 0xFF) ^ 0x5C);
|
|
csn[2] = (uint8_t)((cn >> 16) ^ fc ^ 0x7F);
|
|
csn[3] = (uint8_t)((cn >> 8) ^ (fc << 3) ^ 0xE9);
|
|
csn[4] = 0xF7;
|
|
csn[5] = 0xFF;
|
|
csn[6] = 0x12;
|
|
csn[7] = 0xE0;
|
|
}
|
|
|
|
// New diversified KD/KC for the new CSN
|
|
uint8_t new_kd[8], new_kc[8];
|
|
if (rawkey) {
|
|
memcpy(new_kd, kd_master, 8);
|
|
memcpy(new_kc, kc_master, 8);
|
|
} else {
|
|
HFiClassCalcDivKey(csn, kd_master, new_kd, elite);
|
|
HFiClassCalcDivKey(csn, kc_master, new_kc, elite);
|
|
}
|
|
|
|
// New credential blocks 6-9 for the new FC/CN
|
|
uint8_t new_cred[32] = {
|
|
0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
if (format_idx != -1) {
|
|
wiegand_message_t packed;
|
|
memset(&packed, 0, sizeof(wiegand_message_t));
|
|
if (HIDPack(format_idx, &card, &packed, false)) {
|
|
packed.Length++;
|
|
set_bit_by_position(&packed, true, 0);
|
|
#ifdef HOST_LITTLE_ENDIAN
|
|
packed.Mid = BSWAP_32(packed.Mid);
|
|
packed.Bot = BSWAP_32(packed.Bot);
|
|
#endif
|
|
memcpy(new_cred + 8, &packed.Mid, sizeof(packed.Mid));
|
|
memcpy(new_cred + 12, &packed.Bot, sizeof(packed.Bot));
|
|
}
|
|
}
|
|
|
|
if (have_enc_key) {
|
|
if (use_sc) {
|
|
Encrypt(new_cred + 8, new_cred + 8);
|
|
Encrypt(new_cred + 16, new_cred + 16);
|
|
Encrypt(new_cred + 24, new_cred + 24);
|
|
} else {
|
|
iclass_encrypt_block_data(new_cred + 8, enc_key);
|
|
iclass_encrypt_block_data(new_cred + 16, enc_key);
|
|
iclass_encrypt_block_data(new_cred + 24, enc_key);
|
|
}
|
|
}
|
|
|
|
// Push only the changed blocks to emulator memory, then set reload flag
|
|
iclass_emul_write_block_silent(0, csn); // block 0: CSN
|
|
iclass_emul_write_block_silent(3, new_kd); // block 3: KD
|
|
iclass_emul_write_block_silent(4, new_kc); // block 4: KC
|
|
for (int b = 0; b < 4; b++) {
|
|
iclass_emul_write_block_silent(6 + b, new_cred + b * 8); // blocks 6-9
|
|
}
|
|
iclass_emul_set_reload_flag(); // signal ARM to reload on next ACTALL
|
|
|
|
PrintAndLogEx(INFO, "FC: " _YELLOW_("%u") " CN: " _YELLOW_("%u") " CSN: " _YELLOW_("%s"),
|
|
card.FacilityCode, card.CardNumber, sprint_hex(csn, 8));
|
|
}
|
|
|
|
tagsim_rawmode_exit();
|
|
|
|
if (!arm_ended) {
|
|
// Client exited the loop (Enter/Esc) but the ARM is still simulating.
|
|
// Tell it to stop and consume the resulting CMD_ACK so the ARM is
|
|
// cleanly back in the main loop before we return.
|
|
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
|
|
WaitForResponseTimeout(CMD_ACK, &resp, 2000);
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass esave -h") "` to save the emulator memory to file");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassInfo(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass info",
|
|
"Act as a iCLASS reader. Reads / fingerprints a iCLASS tag.",
|
|
"hf iclass info");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool shallow_mod = arg_get_lit(ctx, 1);
|
|
CLIParserFree(ctx);
|
|
return info_iclass(shallow_mod);
|
|
}
|
|
|
|
int read_iclass_csn(bool loop, bool verbose, bool shallow_mod) {
|
|
|
|
iclass_card_select_t payload = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support for reader mode yet
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
int res = PM3_SUCCESS;
|
|
|
|
do {
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload, sizeof(iclass_card_select_t));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000)) {
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
if (loop) {
|
|
if (resp.status == PM3_ERFTRANS) {
|
|
continue;
|
|
}
|
|
} else {
|
|
|
|
if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
|
|
if (verbose) PrintAndLogEx(WARNING, "iCLASS / Picopass card select failed ( %d , %d)", r->status, resp.status);
|
|
res = PM3_EOPABORTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
picopass_hdr_t *card = calloc(1, sizeof(picopass_hdr_t));
|
|
if (card == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
DropField();
|
|
return PM3_EMALLOC;
|
|
} else {
|
|
memcpy(card, &r->header.hdr, sizeof(picopass_hdr_t));
|
|
if (loop == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
|
|
iclass_set_last_known_card(card);
|
|
free(card);
|
|
res = PM3_SUCCESS;
|
|
}
|
|
}
|
|
} while (loop && (kbd_enter_pressed() == false));
|
|
|
|
DropField();
|
|
return res;
|
|
}
|
|
|
|
static int CmdHFiClassReader(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass reader",
|
|
"Act as a iCLASS reader. Look for iCLASS tags until Enter or the pm3 button is pressed",
|
|
"hf iclass reader -@ -> continuous reader mode"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("@", NULL, "optional - continuous reader mode"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool cm = arg_get_lit(ctx, 1);
|
|
bool shallow_mod = arg_get_lit(ctx, 2);
|
|
CLIParserFree(ctx);
|
|
|
|
if (cm) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
|
}
|
|
|
|
return read_iclass_csn(cm, false, shallow_mod);
|
|
}
|
|
|
|
static int CmdHFiClassELoad(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass eload",
|
|
"Load emulator memory with data from (bin/json) iCLASS dump file",
|
|
"hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.json\n"
|
|
"hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.bin -m\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_lit0("m", "mem", "use RDV4 spiffs"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
if (strlen(filename) == 0) {
|
|
PrintAndLogEx(ERR, "Error: Please specify a filename");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool use_spiffs = arg_get_lit(ctx, 2);
|
|
bool verbose = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
// use RDV4 spiffs
|
|
if (use_spiffs && IfPm3Flash() == false) {
|
|
PrintAndLogEx(WARNING, "Device not compiled to support spiffs");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (use_spiffs) {
|
|
|
|
if (fnlen > 32) {
|
|
PrintAndLogEx(WARNING, "filename too long for spiffs, expected 32, got %u", fnlen);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_SPIFFS_ELOAD, (uint8_t *)filename, fnlen);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_SPIFFS_ELOAD, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "timeout while waiting for reply");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "Loading file from spiffs to emulatore memory failed");
|
|
return PM3_EFLASH;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "File transfered from spiffs to device emulator memory");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// read dump file
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 2048;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
uint8_t *newdump = realloc(dump, bytes_read);
|
|
if (newdump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(dump);
|
|
return PM3_EMALLOC;
|
|
} else {
|
|
dump = newdump;
|
|
}
|
|
|
|
if (verbose) {
|
|
print_picopass_header((picopass_hdr_t *) dump);
|
|
print_picopass_info((picopass_hdr_t *) dump);
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
//Send to device
|
|
uint16_t bytes_sent = 0;
|
|
iclass_upload_emul(dump, bytes_read, 0, &bytes_sent);
|
|
free(dump);
|
|
PrintAndLogEx(SUCCESS, "uploaded " _YELLOW_("%d") " bytes to emulator memory", bytes_sent);
|
|
PrintAndLogEx(HINT, "Hint: You are ready to simulate. See `" _YELLOW_("hf iclass sim -h") "`");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassESave(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass esave",
|
|
"Save emulator memory to file (bin/json)\n"
|
|
"if filename is not supplied, CSN will be used.",
|
|
"hf iclass esave\n"
|
|
"hf iclass esave -f hf-iclass-dump\n"
|
|
"hf iclass esave -s 2048 -f hf-iclass-dump");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_int0("s", "size", "<256|2048>", "number of bytes to save (default 256)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
uint16_t bytes = arg_get_int_def(ctx, 2, 256);
|
|
|
|
if (bytes > 4096) {
|
|
PrintAndLogEx(WARNING, "Emulator memory is max 4096bytes. Truncating %u to 4096", bytes);
|
|
bytes = 4096;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t *dump = calloc(bytes, sizeof(uint8_t));
|
|
if (dump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "downloading from emulator memory");
|
|
if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) {
|
|
PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
|
|
free(dump);
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
// user supplied filename?
|
|
if (fnlen < 1) {
|
|
char *fptr = filename;
|
|
fptr += snprintf(fptr, sizeof(filename), "hf-iclass-");
|
|
FillFileNameByUID(fptr, dump, "-dump", 8);
|
|
}
|
|
|
|
pm3_save_dump(filename, dump, bytes, jsfIclass);
|
|
free(dump);
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass view -f") "` to view dump file");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassEView(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass eview",
|
|
"Display emulator memory.\n"
|
|
"Number of bytes to download defaults to 256. Other value is 2048.",
|
|
"hf iclass eview\n"
|
|
"hf iclass eview -s 2048\n"
|
|
"hf iclass eview -s 2048 -v");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0("s", "size", "<256|2048>", "number of bytes to save (default 256)"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint16_t blocks = 32;
|
|
uint16_t bytes = arg_get_int_def(ctx, 1, 256);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool dense_output = g_session.dense_output || arg_get_lit(ctx, 3);
|
|
blocks = bytes / 8;
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (bytes > 4096) {
|
|
PrintAndLogEx(WARNING, "Emulator memory is max 4096bytes. Truncating %u to 4096", bytes);
|
|
bytes = 4096;
|
|
}
|
|
|
|
if (bytes % 8 != 0) {
|
|
bytes &= 0xFFF8;
|
|
PrintAndLogEx(WARNING, "Number not divided by 8, truncating to %u", bytes);
|
|
}
|
|
|
|
uint8_t *dump = calloc(bytes, sizeof(uint8_t));
|
|
if (dump == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "downloading from emulator memory");
|
|
if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) {
|
|
PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
|
|
free(dump);
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (verbose) {
|
|
print_picopass_header((picopass_hdr_t *) dump);
|
|
print_picopass_info((picopass_hdr_t *) dump);
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
printIclassDumpContents(dump, 1, blocks, bytes, dense_output);
|
|
print_iclass_sio(dump, bytes, verbose);
|
|
|
|
free(dump);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassESetBlk(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass esetblk",
|
|
"Sets an individual block in emulator memory.",
|
|
"hf iclass esetblk --blk 7 -d 0000000000000000");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int1(NULL, "blk", "<dec>", "block number"),
|
|
arg_str0("d", "data", "<hex>", "bytes to write, 8 hex bytes"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int blk = arg_get_int_def(ctx, 1, 0);
|
|
|
|
if (blk > 255 || blk < 0) {
|
|
PrintAndLogEx(WARNING, "block number must be between 0 and 255. Got " _RED_("%i"), blk);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t data[PICOPASS_BLOCK_SIZE] = {0x00};
|
|
int datalen = 0;
|
|
int res = CLIParamHexToBuf(arg_get_str(ctx, 2), data, sizeof(data), &datalen);
|
|
CLIParserFree(ctx);
|
|
|
|
if (res) {
|
|
PrintAndLogEx(FAILED, "Error parsing bytes");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (datalen != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(WARNING, "block data must include 8 HEX bytes. Got " _RED_("%i"), datalen);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint16_t bytes_sent = 0;
|
|
iclass_upload_emul(data, sizeof(data), blk * PICOPASS_BLOCK_SIZE, &bytes_sent);
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static bool iclass_detect_new_pacs(uint8_t *d) {
|
|
uint8_t n = 0;
|
|
while (n++ < (PICOPASS_BLOCK_SIZE >> 1)) {
|
|
if (d[n] && d[n + 1] == 0xA6) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// block 7 decoder for PACS
|
|
static int iclass_decode_credentials_new_pacs(uint8_t *d) {
|
|
|
|
uint8_t offset = 0;
|
|
while (d[offset] == 0 && (offset < PICOPASS_BLOCK_SIZE / 2)) {
|
|
offset++;
|
|
}
|
|
|
|
uint8_t pad = d[offset];
|
|
|
|
PrintAndLogEx(DEBUG, "%u , %u", offset, pad);
|
|
|
|
char *binstr = (char *)calloc((PICOPASS_BLOCK_SIZE * 8) + 1, sizeof(uint8_t));
|
|
if (binstr == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
uint8_t n = PICOPASS_BLOCK_SIZE - offset - 2;
|
|
bytes_2_binstr(binstr, d + offset + 2, n);
|
|
|
|
PrintAndLogEx(DEBUG, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(d + offset + 2, n));
|
|
PrintAndLogEx(DEBUG, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));
|
|
|
|
binstr[strlen(binstr) - pad] = '\0';
|
|
PrintAndLogEx(DEBUG, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));
|
|
|
|
size_t hexlen = 0;
|
|
uint8_t hex[16] = {0};
|
|
binstr_2_bytes(hex, &hexlen, binstr);
|
|
PrintAndLogEx(DEBUG, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen));
|
|
|
|
uint32_t top = 0, mid = 0, bot = 0;
|
|
if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
|
|
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
|
|
free(binstr);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
free(binstr);
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ----------------------------");
|
|
decode_wiegand(top, mid, bot, 0);
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void iclass_decode_credentials(uint8_t *data) {
|
|
picopass_hdr_t *hdr = (picopass_hdr_t *)data;
|
|
if (memcmp(hdr->app_issuer_area, empty, PICOPASS_BLOCK_SIZE)) {
|
|
// Not a Legacy or SR card, nothing to do here.
|
|
return;
|
|
}
|
|
|
|
BLOCK79ENCRYPTION encryption = (data[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);
|
|
|
|
uint8_t *b7 = data + (PICOPASS_BLOCK_SIZE * 7);
|
|
|
|
bool has_new_pacs = iclass_detect_new_pacs(b7);
|
|
bool has_values = (memcmp(b7, empty, PICOPASS_BLOCK_SIZE) != 0) && (memcmp(b7, zeros, PICOPASS_BLOCK_SIZE) != 0);
|
|
if (has_values && encryption == None) {
|
|
|
|
PrintAndLogEx(INFO, "------------------------ " _CYAN_("Block 7 decoder") " --------------------------");
|
|
|
|
// todo: remove preamble/sentinel
|
|
if (has_new_pacs) {
|
|
iclass_decode_credentials_new_pacs(b7);
|
|
} else {
|
|
char hexstr[16 + 1] = {0};
|
|
hex_to_buffer((uint8_t *)hexstr, b7, PICOPASS_BLOCK_SIZE, sizeof(hexstr) - 1, 0, 0, true);
|
|
|
|
uint32_t top = 0, mid = 0, bot = 0;
|
|
hexstring_to_u96(&top, &mid, &bot, hexstr);
|
|
|
|
char binstr[64 + 1];
|
|
hextobinstring(binstr, hexstr);
|
|
char *pbin = binstr;
|
|
// Strip leading zeros
|
|
while (strlen(pbin) && *(++pbin) == '0');
|
|
|
|
size_t binlen = strlen(pbin);
|
|
|
|
// Check if we have a sentinel bit (leading '1' that makes length one more than common formats)
|
|
// Common formats: 26, 30, 33, 34, 35, 36, 37, 46, 48
|
|
// If we have 27, 31, 34, 35, 36, 37, 38, 47, 49 bits and it starts with '1',
|
|
// it's likely a sentinel bit that should be stripped
|
|
if (binlen > 0 && pbin[0] == '1' &&
|
|
(binlen == 27 || binlen == 31 || binlen == 34 || binlen == 35 ||
|
|
binlen == 36 || binlen == 37 || binlen == 38 || binlen == 47 || binlen == 49)) {
|
|
// Strip the sentinel bit by recreating u96 from binary string without leading '1'
|
|
char *corrected_bin = pbin + 1; // Skip the leading '1'
|
|
size_t corrected_len = strlen(corrected_bin);
|
|
|
|
// Recreate u96 values from corrected binary string
|
|
top = 0;
|
|
mid = 0;
|
|
bot = 0;
|
|
binstring_to_u96(&top, &mid, &bot, corrected_bin);
|
|
|
|
pbin = corrected_bin;
|
|
binlen = corrected_len;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Bin... " _GREEN_("%s") " ( %zu )", pbin, binlen);
|
|
PrintAndLogEx(NORMAL, "");
|
|
// Use the corrected length (without sentinel) for decoding
|
|
decode_wiegand(top, mid, bot, (int)binlen);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int CmdHFiClassDecrypt(const char *Cmd) {
|
|
CLIParserContext *clictx;
|
|
CLIParserInit(&clictx, "hf iclass decrypt",
|
|
"3DES decrypt data\n"
|
|
"This is a naive implementation, it tries to decrypt every block after block 6.\n"
|
|
"Correct behaviour would be to decrypt only the application areas where the key is valid,\n"
|
|
"which is defined by the configuration block.\n"
|
|
"\nOBS!\n"
|
|
"In order to use this function, the file `iclass_decryptionkey.bin` must reside\n"
|
|
"in the resources directory. The file must be 16 bytes binary data\n"
|
|
"or...\n"
|
|
"make sure your cardhelper is placed in the sim module",
|
|
"hf iclass decrypt -f hf-iclass-AA162D30F8FF12F1-dump.bin\n"
|
|
"hf iclass decrypt -f hf-iclass-AA162D30F8FF12F1-dump.bin -k 000102030405060708090a0b0c0d0e0f\n"
|
|
"hf iclass decrypt -d 1122334455667788 -k 000102030405060708090a0b0c0d0e0f");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_str0("d", "data", "<hex>", "3DES encrypted data"),
|
|
arg_str0("k", "key", "<hex>", "3DES transport key"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "d6", "decode as block 6"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_lit0(NULL, "ns", "no save to file"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(clictx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(clictx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int enc_data_len = 0;
|
|
uint8_t enc_data[PICOPASS_BLOCK_SIZE] = {0};
|
|
bool have_data = false;
|
|
|
|
CLIGetHexWithReturn(clictx, 2, enc_data, &enc_data_len);
|
|
|
|
int key_len = 0;
|
|
uint8_t key[16] = {0};
|
|
uint8_t *keyptr = NULL;
|
|
bool have_key = false;
|
|
|
|
CLIGetHexWithReturn(clictx, 3, key, &key_len);
|
|
|
|
bool verbose = arg_get_lit(clictx, 4);
|
|
bool use_decode6 = arg_get_lit(clictx, 5);
|
|
bool dense_output = g_session.dense_output || arg_get_lit(clictx, 6);
|
|
bool nosave = arg_get_lit(clictx, 7);
|
|
CLIParserFree(clictx);
|
|
|
|
// sanity checks
|
|
if (enc_data_len > 0) {
|
|
if (enc_data_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "Data must be 8 hex bytes (16 HEX symbols)");
|
|
return PM3_EINVARG;
|
|
}
|
|
have_data = true;
|
|
}
|
|
|
|
if (key_len > 0) {
|
|
if (key_len != 16) {
|
|
PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
|
|
return PM3_EINVARG;
|
|
}
|
|
have_key = true;
|
|
}
|
|
|
|
size_t decryptedlen = 2048;
|
|
uint8_t *decrypted = NULL;
|
|
bool have_file = false;
|
|
int res = PM3_SUCCESS;
|
|
|
|
// if user supplied dump file, time to load it
|
|
if (fnlen > 0) {
|
|
|
|
// read dump file
|
|
res = pm3_load_dump(filename, (void **)&decrypted, &decryptedlen, 2048);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
have_file = true;
|
|
}
|
|
|
|
// load transport key
|
|
bool use_sc = false;
|
|
if (have_key == false) {
|
|
use_sc = IsCardHelperPresent(verbose);
|
|
if (use_sc == false) {
|
|
size_t keylen = 0;
|
|
res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "Couldn't find any decryption methods");
|
|
free(decrypted);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (keylen != 16) {
|
|
PrintAndLogEx(ERR, "Failed to load transport key from file");
|
|
free(keyptr);
|
|
free(decrypted);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(key, keyptr, sizeof(key));
|
|
free(keyptr);
|
|
}
|
|
}
|
|
|
|
// tripledes
|
|
mbedtls_des3_context ctx;
|
|
mbedtls_des3_set2key_dec(&ctx, key);
|
|
|
|
// decrypt user supplied data
|
|
if (have_data) {
|
|
|
|
uint8_t dec_data[PICOPASS_BLOCK_SIZE] = {0};
|
|
if (use_sc) {
|
|
Decrypt(enc_data, dec_data);
|
|
} else {
|
|
mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "encrypted... %s", sprint_hex_inrow(enc_data, sizeof(enc_data)));
|
|
PrintAndLogEx(SUCCESS, "plain....... " _YELLOW_("%s"), sprint_hex_inrow(dec_data, sizeof(dec_data)));
|
|
|
|
if (use_sc && use_decode6) {
|
|
DecodeBlock6(dec_data);
|
|
}
|
|
}
|
|
|
|
// decrypt dump file data
|
|
if (have_file) {
|
|
|
|
picopass_hdr_t *hdr = (picopass_hdr_t *)decrypted;
|
|
|
|
uint8_t mem = hdr->conf.mem_config;
|
|
uint8_t chip = hdr->conf.chip_config;
|
|
uint8_t applimit = hdr->conf.app_limit;
|
|
uint8_t kb = 2;
|
|
uint8_t app_areas = 2;
|
|
uint8_t books = 1;
|
|
uint8_t pages = 1;
|
|
getMemConfig(mem, chip, &app_areas, &kb, &books, &pages);
|
|
|
|
BLOCK79ENCRYPTION aa1_encryption = (decrypted[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);
|
|
|
|
uint8_t limit = MIN(applimit, decryptedlen / 8);
|
|
|
|
if (decryptedlen / PICOPASS_BLOCK_SIZE != applimit) {
|
|
PrintAndLogEx(WARNING, "Actual file len " _YELLOW_("%zu") " vs HID app-limit len " _YELLOW_("%u"), decryptedlen, applimit * PICOPASS_BLOCK_SIZE);
|
|
PrintAndLogEx(INFO, "Setting limit to " _GREEN_("%u"), limit * PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
//uint8_t numblocks4userid = GetNumberBlocksForUserId(decrypted + (6 * 8));
|
|
|
|
bool decrypted_block789 = false;
|
|
for (uint8_t blocknum = 0; blocknum < limit; ++blocknum) {
|
|
|
|
uint16_t idx = blocknum * PICOPASS_BLOCK_SIZE;
|
|
memcpy(enc_data, decrypted + idx, PICOPASS_BLOCK_SIZE);
|
|
|
|
switch (aa1_encryption) {
|
|
// Right now, only 3DES is supported
|
|
case TRIPLEDES:
|
|
// Decrypt block 7,8,9 if configured.
|
|
if (blocknum > 6 && blocknum <= 9 && memcmp(enc_data, empty, PICOPASS_BLOCK_SIZE) != 0) {
|
|
if (use_sc) {
|
|
Decrypt(enc_data, decrypted + idx);
|
|
} else {
|
|
mbedtls_des3_crypt_ecb(&ctx, enc_data, decrypted + idx);
|
|
}
|
|
decrypted_block789 = true;
|
|
}
|
|
break;
|
|
case DES:
|
|
case RFU:
|
|
case None:
|
|
// Nothing to do for None anyway...
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (decrypted_block789) {
|
|
// Set the 2 last bits of block6 to 0 to mark the data as decrypted
|
|
decrypted[(6 * PICOPASS_BLOCK_SIZE) + 7] &= 0xFC;
|
|
}
|
|
}
|
|
|
|
if (nosave) {
|
|
PrintAndLogEx(INFO, "Called with no save option");
|
|
PrintAndLogEx(NORMAL, "");
|
|
} else {
|
|
|
|
// use the first block (CSN) for filename
|
|
char *fptr = calloc(50, sizeof(uint8_t));
|
|
if (fptr == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(decrypted);
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
strcat(fptr, "hf-iclass-");
|
|
FillFileNameByUID(fptr, hdr->csn, "-dump-decrypted", sizeof(hdr->csn));
|
|
|
|
pm3_save_dump(fptr, decrypted, decryptedlen, jsfIclass);
|
|
free(fptr);
|
|
}
|
|
|
|
printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen, dense_output);
|
|
print_iclass_sio(decrypted, decryptedlen, verbose);
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// decode block 6
|
|
bool has_values = (memcmp(decrypted + (PICOPASS_BLOCK_SIZE * 6), empty, 8) != 0) && (memcmp(decrypted + (PICOPASS_BLOCK_SIZE * 6), zeros, PICOPASS_BLOCK_SIZE) != 0);
|
|
if (has_values && use_sc) {
|
|
DecodeBlock6(decrypted + (PICOPASS_BLOCK_SIZE * 6));
|
|
}
|
|
|
|
// decode block 7-8-9
|
|
iclass_decode_credentials(decrypted);
|
|
|
|
// decode block 9
|
|
has_values = (memcmp(decrypted + (PICOPASS_BLOCK_SIZE * 9), empty, PICOPASS_BLOCK_SIZE) != 0) && (memcmp(decrypted + (PICOPASS_BLOCK_SIZE * 9), zeros, PICOPASS_BLOCK_SIZE) != 0);
|
|
if (has_values && use_sc) {
|
|
uint8_t usr_blk_len = GetNumberBlocksForUserId(decrypted + (PICOPASS_BLOCK_SIZE * 6));
|
|
if (usr_blk_len < 3) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Block 9 decoder");
|
|
|
|
uint8_t pinsize = GetPinSize(decrypted + (PICOPASS_BLOCK_SIZE * 6));
|
|
if (pinsize > 0) {
|
|
|
|
uint64_t pin = bytes_to_num(decrypted + (PICOPASS_BLOCK_SIZE * 9), 5);
|
|
char tmp[17] = {0};
|
|
snprintf(tmp, sizeof(tmp), "%."PRIu64, BCD2DEC(pin));
|
|
PrintAndLogEx(INFO, "PIN........................ " _GREEN_("%.*s"), pinsize, tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "-------------------------------------------------------------------");
|
|
free(decrypted);
|
|
}
|
|
|
|
mbedtls_des3_free(&ctx);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassEncryptBlk(const char *Cmd) {
|
|
CLIParserContext *clictx;
|
|
CLIParserInit(&clictx, "hf iclass encrypt",
|
|
"3DES encrypt data\n"
|
|
"OBS! In order to use this function, the file 'iclass_decryptionkey.bin' must reside\n"
|
|
"in the resources directory. The file should be 16 hex bytes of binary data",
|
|
"hf iclass encrypt -d 0102030405060708\n"
|
|
"hf iclass encrypt -d 0102030405060708 -k 00112233445566778899AABBCCDDEEFF");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("d", "data", "<hex>", "data to encrypt"),
|
|
arg_str0("k", "key", "<hex>", "3DES transport key"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(clictx, Cmd, argtable, false);
|
|
|
|
int blk_data_len = 0;
|
|
uint8_t blk_data[8] = {0};
|
|
|
|
CLIGetHexWithReturn(clictx, 1, blk_data, &blk_data_len);
|
|
|
|
if (blk_data_len != 8) {
|
|
PrintAndLogEx(ERR, "Block data must be 8 hex bytes (16 HEX symbols)");
|
|
CLIParserFree(clictx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int key_len = 0;
|
|
uint8_t key[16] = {0};
|
|
uint8_t *keyptr = NULL;
|
|
bool have_key = false;
|
|
|
|
CLIGetHexWithReturn(clictx, 2, key, &key_len);
|
|
|
|
if (key_len > 0) {
|
|
if (key_len != 16) {
|
|
PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
|
|
CLIParserFree(clictx);
|
|
return PM3_EINVARG;
|
|
}
|
|
have_key = true;
|
|
}
|
|
|
|
bool verbose = arg_get_lit(clictx, 3);
|
|
|
|
CLIParserFree(clictx);
|
|
|
|
bool use_sc = false;
|
|
if (have_key == false) {
|
|
use_sc = IsCardHelperPresent(verbose);
|
|
if (use_sc == false) {
|
|
size_t keylen = 0;
|
|
int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to find any encryption methods");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (keylen != 16) {
|
|
PrintAndLogEx(ERR, "Failed to load transport key from file");
|
|
free(keyptr);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(key, keyptr, sizeof(key));
|
|
free(keyptr);
|
|
}
|
|
}
|
|
|
|
|
|
PrintAndLogEx(SUCCESS, "plain....... %s", sprint_hex_inrow(blk_data, sizeof(blk_data)));
|
|
|
|
if (use_sc) {
|
|
Encrypt(blk_data, blk_data);
|
|
} else {
|
|
iclass_encrypt_block_data(blk_data, key);
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "encrypted... " _YELLOW_("%s"), sprint_hex_inrow(blk_data, sizeof(blk_data)));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static bool select_only(uint8_t *CSN, uint8_t *CCNR, bool verbose, bool shallow_mod) {
|
|
|
|
iclass_card_select_t payload = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support here yet
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload, sizeof(iclass_card_select_t));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return false;
|
|
}
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
|
|
// no tag found or button pressed
|
|
if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
|
|
if (verbose) {
|
|
PrintAndLogEx(FAILED, "failed tag-select, aborting... (%d)", r->status);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (CSN != NULL) {
|
|
memcpy(CSN, hdr->csn, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
if (CCNR != NULL) {
|
|
memcpy(CCNR, hdr->epurse, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "CSN............ %s", sprint_hex_inrow(CSN, PICOPASS_BLOCK_SIZE));
|
|
PrintAndLogEx(SUCCESS, "E-purse........ %s", sprint_hex_inrow(CCNR, PICOPASS_BLOCK_SIZE));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// iclass_dump_non_secure - read all blocks from a non-secure page mode iCLASS tag.
|
|
// Returns PM3_SUCCESS on success. On success, *tag_data is filled with the
|
|
// full tag memory (0x100 * 8 bytes) and *taglen is set to the number of
|
|
// valid bytes (= (app_limit1 + 1) * 8).
|
|
// Caller must pass a zeroed/initialised buffer of at least 0x100 * 8 bytes.
|
|
static int iclass_dump_non_secure(bool shallow_mod, uint8_t *tag_data, uint16_t *taglen) {
|
|
|
|
iclass_card_select_t payload_rdr = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support here yet
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload_rdr.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t));
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
if (resp.status == PM3_ERFTRANS) {
|
|
PrintAndLogEx(FAILED, "no tag found");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
if (r->status == FLAG_ICLASS_NULL) {
|
|
PrintAndLogEx(FAILED, "failed to read block 0,1,2");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
|
|
// copy blocks 0-2 (CSN, config, e-purse) already returned by reader command
|
|
memcpy(tag_data, hdr, 24);
|
|
|
|
uint8_t type = get_mem_config(hdr);
|
|
uint8_t app_limit1 = card_app2_limit[type];
|
|
|
|
PrintAndLogEx(INFO, "Non-secure page mode");
|
|
PrintAndLogEx(INFO, "Dumping all available memory, block 3 - %u (0x%02x)", app_limit1, app_limit1);
|
|
|
|
iclass_dump_req_t payload = {
|
|
.req.use_raw = false,
|
|
.req.use_elite = false,
|
|
.req.use_credit_key = false,
|
|
.req.use_replay = false,
|
|
.req.send_reply = true,
|
|
.req.do_auth = false,
|
|
.req.shallow_mod = shallow_mod,
|
|
.start_block = 3,
|
|
.end_block = app_limit1,
|
|
.page = 0 // no page selection support here yet
|
|
};
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_DUMP, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
while (true) {
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
|
|
DropField();
|
|
return PM3_EOPABORTED;
|
|
}
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_DUMP, &resp, 2000)) {
|
|
break;
|
|
}
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "failed to communicate with card");
|
|
return resp.status;
|
|
}
|
|
|
|
struct p_resp {
|
|
bool isOK;
|
|
uint16_t block_cnt;
|
|
uint32_t bb_offset;
|
|
} PACKED;
|
|
struct p_resp *packet = (struct p_resp *)resp.data.asBytes;
|
|
|
|
if (packet->isOK == false) {
|
|
PrintAndLogEx(WARNING, "read blocks failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint32_t startindex = packet->bb_offset;
|
|
uint32_t blocks_read = packet->block_cnt;
|
|
|
|
uint8_t tempbuf[0x100 * PICOPASS_BLOCK_SIZE];
|
|
if (GetFromDevice(BIG_BUF, tempbuf, sizeof(tempbuf), startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
memcpy(tag_data + (PICOPASS_BLOCK_SIZE * 3),
|
|
tempbuf + (PICOPASS_BLOCK_SIZE * 3),
|
|
blocks_read * PICOPASS_BLOCK_SIZE);
|
|
|
|
*taglen = (app_limit1 + 1) * PICOPASS_BLOCK_SIZE;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassDump(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass dump",
|
|
"Dump all memory from a iCLASS tag",
|
|
"hf iclass dump -k 001122334455667B\n"
|
|
"hf iclass dump -k AAAAAAAAAAAAAAAA --credit 001122334455667B\n"
|
|
"hf iclass dump -k AAAAAAAAAAAAAAAA --elite\n"
|
|
"hf iclass dump --ki 0\n"
|
|
"hf iclass dump --ki 0 --ci 2");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "save filename"),
|
|
arg_str0("k", "key", "<hex>", "debit key or NR/MAC for replay as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "debit key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_str0(NULL, "credit", "<hex>", "credit key as 8 hex bytes"),
|
|
arg_int0(NULL, "ci", "<dec>", "credit key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "raw, the key is interpreted as raw block 3/4"),
|
|
arg_lit0(NULL, "nr", "replay of NR/MAC"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_lit0(NULL, "force", "force unsecure card read"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0(NULL, "ns", "no save to file"),
|
|
arg_int0(NULL, "page", "<dec>", "which page to dump from"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int key_len = 0;
|
|
uint8_t key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, key, &key_len);
|
|
|
|
int deb_key_nr = arg_get_int_def(ctx, 3, -1);
|
|
|
|
int credit_key_len = 0;
|
|
uint8_t credit_key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, credit_key, &credit_key_len);
|
|
|
|
int credit_key_nr = arg_get_int_def(ctx, 5, -1);
|
|
bool elite = arg_get_lit(ctx, 6);
|
|
bool rawkey = arg_get_lit(ctx, 7);
|
|
bool use_replay = arg_get_lit(ctx, 8);
|
|
bool dense_output = g_session.dense_output || arg_get_lit(ctx, 9);
|
|
bool force = arg_get_lit(ctx, 10);
|
|
bool shallow_mod = arg_get_lit(ctx, 11);
|
|
bool nosave = arg_get_lit(ctx, 12);
|
|
int page = arg_get_int_def(ctx, 13, 0);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
bool auth = false;
|
|
bool have_credit_key = false;
|
|
|
|
// Sanity checks
|
|
|
|
if (key_len > 0 && deb_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify debit key or index, not both");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (key_len > 0) {
|
|
auth = true;
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Debit key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (deb_key_nr >= 0) {
|
|
if (deb_key_nr < ICLASS_KEYS_MAX) {
|
|
auth = true;
|
|
memcpy(key, iClass_Key_Table[deb_key_nr], 8);
|
|
PrintAndLogEx(SUCCESS, "Using AA1 (debit) key[%d] " _GREEN_("%s"), deb_key_nr, sprint_hex(iClass_Key_Table[deb_key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (credit_key_len > 0 && credit_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify credit key or index, not both");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (credit_key_len > 0) {
|
|
auth = true;
|
|
have_credit_key = true;
|
|
if (credit_key_len != 8) {
|
|
PrintAndLogEx(ERR, "Credit key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (credit_key_nr >= 0) {
|
|
if (credit_key_nr < ICLASS_KEYS_MAX) {
|
|
auth = true;
|
|
have_credit_key = true;
|
|
memcpy(credit_key, iClass_Key_Table[credit_key_nr], 8);
|
|
PrintAndLogEx(SUCCESS, "Using AA2 (credit) key[%d] " _GREEN_("%s"), credit_key_nr, sprint_hex(iClass_Key_Table[credit_key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t app_limit1 = 0, app_limit2 = 0;
|
|
|
|
//get CSN and config
|
|
uint8_t tag_data[0x100 * 8];
|
|
memset(tag_data, 0xFF, sizeof(tag_data));
|
|
|
|
iclass_card_select_t payload_rdr = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = page
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload_rdr.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t));
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
if (resp.status == PM3_ERFTRANS) {
|
|
PrintAndLogEx(FAILED, "no tag found");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
if (r->status == FLAG_ICLASS_NULL) {
|
|
PrintAndLogEx(FAILED, "failed to read block 0,1,2");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
|
|
if (r->status & (FLAG_ICLASS_CSN | FLAG_ICLASS_CONF | FLAG_ICLASS_CC)) {
|
|
|
|
memcpy(tag_data, hdr, 24);
|
|
|
|
uint8_t type = get_mem_config(hdr);
|
|
|
|
// tags configured for NON SECURE PAGE, acts different
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
|
|
PrintAndLogEx(INFO, "Card in non-secure page mode detected");
|
|
|
|
app_limit1 = card_app2_limit[type];
|
|
app_limit2 = 0;
|
|
} else if (hdr->conf.app_limit >= hdr->conf.mem_config) {
|
|
PrintAndLogEx(WARNING, "AA1 config is >= card size, using card size as AA1 limit");
|
|
app_limit1 = card_app2_limit[type];
|
|
} else {
|
|
app_limit1 = hdr->conf.app_limit;
|
|
app_limit2 = card_app2_limit[type];
|
|
}
|
|
}
|
|
|
|
//
|
|
if (force) {
|
|
pagemap = PICOPASS_NON_SECURE_PAGEMODE;
|
|
PrintAndLogEx(INFO, "Forcing NON SECURE PAGE dumping");
|
|
}
|
|
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(INFO, "Dumping all available memory, block 3 - %u (0x%02x)", app_limit1, app_limit1);
|
|
if (auth) {
|
|
PrintAndLogEx(INFO, "No keys needed, ignoring user supplied key");
|
|
}
|
|
} else {
|
|
|
|
if (auth == false) {
|
|
auth = true;
|
|
memcpy(key, iClass_Key_Table[0], 8);
|
|
PrintAndLogEx(SUCCESS, "Default to AA1 (debit) " _GREEN_("%s"), sprint_hex(key, sizeof(key)));
|
|
}
|
|
|
|
if (app_limit2 != 0) {
|
|
PrintAndLogEx(INFO, "Card has at least 2 application areas. AA1 limit %u (0x%02X) AA2 limit %u (0x%02X)", app_limit1, app_limit1, app_limit2, app_limit2);
|
|
} else {
|
|
PrintAndLogEx(INFO, "Card has 1 application area. AA1 limit %u (0x%02X)", app_limit1, app_limit1);
|
|
}
|
|
}
|
|
|
|
iclass_dump_req_t payload = {
|
|
.req.use_raw = rawkey,
|
|
.req.use_elite = elite,
|
|
.req.use_credit_key = false,
|
|
.req.use_replay = use_replay,
|
|
.req.send_reply = true,
|
|
.req.do_auth = auth,
|
|
.req.shallow_mod = shallow_mod,
|
|
.end_block = app_limit1,
|
|
.page = page,
|
|
};
|
|
memcpy(payload.req.key, key, 8);
|
|
|
|
// tags configured for NON SECURE PAGE, acts different
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
payload.start_block = 3;
|
|
payload.req.do_auth = false;
|
|
} else {
|
|
payload.start_block = 5;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_DUMP, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
while (true) {
|
|
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
|
|
DropField();
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_DUMP, &resp, 2000)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "failed to communicate with card");
|
|
return resp.status;
|
|
}
|
|
|
|
struct p_resp {
|
|
bool isOK;
|
|
uint16_t block_cnt;
|
|
uint32_t bb_offset;
|
|
} PACKED;
|
|
struct p_resp *packet = (struct p_resp *)resp.data.asBytes;
|
|
|
|
if (packet->isOK == false) {
|
|
PrintAndLogEx(WARNING, "read AA1 blocks failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint32_t startindex = packet->bb_offset;
|
|
uint32_t blocks_read = packet->block_cnt;
|
|
|
|
uint8_t tempbuf[0x100 * 8];
|
|
|
|
// response ok - now get bigbuf content of the dump
|
|
if (GetFromDevice(BIG_BUF, tempbuf, sizeof(tempbuf), startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (pagemap != PICOPASS_NON_SECURE_PAGEMODE) {
|
|
// div key KD
|
|
memcpy(tag_data + (PICOPASS_BLOCK_SIZE * 3),
|
|
tempbuf + (PICOPASS_BLOCK_SIZE * 3), PICOPASS_BLOCK_SIZE);
|
|
}
|
|
// all memory available
|
|
memcpy(tag_data + (PICOPASS_BLOCK_SIZE * payload.start_block),
|
|
tempbuf + (PICOPASS_BLOCK_SIZE * payload.start_block),
|
|
blocks_read * PICOPASS_BLOCK_SIZE);
|
|
|
|
uint16_t bytes_got = (app_limit1 + 1) * 8;
|
|
|
|
// try AA2 Kc, Credit
|
|
bool aa2_success = false;
|
|
|
|
if (have_credit_key && pagemap != PICOPASS_NON_SECURE_PAGEMODE && app_limit2 != 0) {
|
|
|
|
// AA2 authenticate credit key
|
|
memcpy(payload.req.key, credit_key, 8);
|
|
|
|
payload.req.use_credit_key = true;
|
|
payload.start_block = app_limit1 + 1;
|
|
payload.end_block = app_limit2;
|
|
payload.req.do_auth = true;
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_DUMP, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
while (true) {
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
|
|
DropField();
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_DUMP, &resp, 2000))
|
|
break;
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (resp.status != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "failed to communicate with card");
|
|
goto write_dump;
|
|
}
|
|
|
|
packet = (struct p_resp *)resp.data.asBytes;
|
|
if (packet->isOK == false) {
|
|
PrintAndLogEx(WARNING, "failed read block using credit key");
|
|
goto write_dump;
|
|
}
|
|
|
|
blocks_read = packet->block_cnt;
|
|
startindex = packet->bb_offset;
|
|
|
|
if (blocks_read * 8 > sizeof(tag_data) - bytes_got) {
|
|
PrintAndLogEx(WARNING, "data exceeded buffer size! ");
|
|
blocks_read = (sizeof(tag_data) - bytes_got) / 8;
|
|
}
|
|
|
|
// get dumped data from bigbuf
|
|
if (GetFromDevice(BIG_BUF, tempbuf, sizeof(tempbuf), startindex, NULL, 0, NULL, 2500, false) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
goto write_dump;
|
|
}
|
|
|
|
// div key KC
|
|
memcpy(tag_data + (PICOPASS_BLOCK_SIZE * 4), tempbuf + (PICOPASS_BLOCK_SIZE * 4), PICOPASS_BLOCK_SIZE);
|
|
|
|
// AA2 data
|
|
memcpy(tag_data + (PICOPASS_BLOCK_SIZE * payload.start_block),
|
|
tempbuf + (PICOPASS_BLOCK_SIZE * payload.start_block),
|
|
blocks_read * PICOPASS_BLOCK_SIZE);
|
|
|
|
bytes_got += (blocks_read * PICOPASS_BLOCK_SIZE);
|
|
|
|
aa2_success = true;
|
|
}
|
|
|
|
write_dump:
|
|
|
|
if (have_credit_key && pagemap != 0x01 && aa2_success == false) {
|
|
PrintAndLogEx(INFO, "Reading AA2 failed. dumping AA1 data to file");
|
|
}
|
|
|
|
// print the dump
|
|
printIclassDumpContents(tag_data, 1, (bytes_got / 8), bytes_got, dense_output);
|
|
|
|
if (nosave) {
|
|
PrintAndLogEx(INFO, "Called with no save option");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// use CSN as filename
|
|
if (filename[0] == 0) {
|
|
strcat(filename, "hf-iclass-");
|
|
FillFileNameByUID(filename, tag_data, "-dump", 8);
|
|
}
|
|
|
|
// save the dump to .bin file
|
|
PrintAndLogEx(SUCCESS, "saving dump file - %u blocks read", bytes_got / 8);
|
|
|
|
pm3_save_dump(filename, tag_data, bytes_got, jsfIclass);
|
|
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass decrypt -f") "` to decrypt dump file");
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass view -f") "` to view dump file");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int iclass_write_block(uint8_t blockno, uint8_t *bldata, uint8_t *macdata, uint8_t *KEY, bool use_credit_key,
|
|
bool elite, bool rawkey, bool replay, bool verbose, bool use_secure_pagemode, bool shallow_mod) {
|
|
|
|
iclass_writeblock_req_t payload = {
|
|
.req.use_raw = rawkey,
|
|
.req.use_elite = elite,
|
|
.req.use_credit_key = use_credit_key,
|
|
.req.use_replay = replay,
|
|
.req.blockno = blockno,
|
|
.req.send_reply = true,
|
|
.req.do_auth = use_secure_pagemode,
|
|
.req.shallow_mod = shallow_mod,
|
|
};
|
|
memcpy(payload.req.key, KEY, 8);
|
|
memcpy(payload.data, bldata, sizeof(payload.data));
|
|
|
|
if (replay) {
|
|
memcpy(payload.mac, macdata, sizeof(payload.mac));
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_WRITEBL, (uint8_t *)&payload, sizeof(payload));
|
|
PacketResponseNG resp;
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_WRITEBL, &resp, 2000) == false) {
|
|
if (verbose) PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
|
|
return resp.status;
|
|
}
|
|
|
|
return (resp.data.asBytes[0] == 1) ? PM3_SUCCESS : PM3_ESOFT;
|
|
}
|
|
|
|
static int CmdHFiClass_WriteBlock(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass wrbl",
|
|
"Write data to an iCLASS tag",
|
|
"hf iclass wrbl --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B\n"
|
|
"hf iclass wrbl --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B --credit\n"
|
|
"hf iclass wrbl --blk 10 -d AAAAAAAAAAAAAAAA --ki 0");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int1(NULL, "blk", "<dec>", "block number"),
|
|
arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
|
|
arg_str0("m", "mac", "<hex>", "replay mac data (4 hex bytes)"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_lit0(NULL, "nr", "replay of NR/MAC block write or use privilege escalation if mac is empty"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0("@", NULL, "optional - continuous mode"),
|
|
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;
|
|
}
|
|
|
|
bool auth = false;
|
|
|
|
if (key_len > 0) {
|
|
auth = true;
|
|
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) {
|
|
auth = true;
|
|
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;
|
|
}
|
|
}
|
|
|
|
int blockno = arg_get_int_def(ctx, 3, 0);
|
|
|
|
int data_len = 0;
|
|
uint8_t data[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, data, &data_len);
|
|
|
|
if (data_len != 8) {
|
|
PrintAndLogEx(ERR, "Data must be 8 hex bytes (16 hex symbols)");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int mac_len = 0;
|
|
uint8_t mac[4] = {0};
|
|
CLIGetHexWithReturn(ctx, 5, mac, &mac_len);
|
|
|
|
if (mac_len) {
|
|
if (mac_len != 4) {
|
|
PrintAndLogEx(ERR, "MAC must be 4 hex bytes (8 hex symbols)");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
|
|
bool use_credit_key = arg_get_lit(ctx, 6);
|
|
bool elite = arg_get_lit(ctx, 7);
|
|
bool rawkey = arg_get_lit(ctx, 8);
|
|
bool use_replay = arg_get_lit(ctx, 9);
|
|
bool verbose = arg_get_lit(ctx, 10);
|
|
bool shallow_mod = arg_get_lit(ctx, 11);
|
|
bool cm = arg_get_lit(ctx, 12);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (cm) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
|
}
|
|
|
|
int isok = 0;
|
|
do {
|
|
isok = iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
|
|
switch (isok) {
|
|
case PM3_SUCCESS: {
|
|
PrintAndLogEx(SUCCESS, "Wrote block " _YELLOW_("%d") " / " _YELLOW_("0x%02X") " ( " _GREEN_("ok") " )", blockno, blockno);
|
|
break;
|
|
}
|
|
case PM3_ETEAROFF: {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Writing tear off triggered");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
PrintAndLogEx(FAILED, "Writing failed");
|
|
break;
|
|
}
|
|
}
|
|
} while (cm && (kbd_enter_pressed() == false));
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
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 --ki 0 -d FEFFFEFF");
|
|
|
|
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 4 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) == false) {
|
|
if (verbose) PrintAndLogEx(WARNING, "command execution time out");
|
|
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",
|
|
"Restore data from dumpfile (bin/eml/json) onto a iCLASS tag",
|
|
"hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0\n"
|
|
"hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0 --elite\n"
|
|
"hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 -k 1122334455667788 --elite\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "specify a filename to restore"),
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int1(NULL, "first", "<dec>", "The first block number to restore"),
|
|
arg_int1(NULL, "last", "<dec>", "The last block number to restore"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
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_lit0(NULL, "nr", "replay of nr mac with privilege escalation"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int key_len = 0;
|
|
uint8_t key[8] = {0};
|
|
|
|
CLIGetHexWithReturn(ctx, 2, key, &key_len);
|
|
|
|
int key_nr = arg_get_int_def(ctx, 3, -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, "Please specify a key or key index");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int startblock = arg_get_int_def(ctx, 4, 0);
|
|
int endblock = arg_get_int_def(ctx, 5, 0);
|
|
|
|
bool use_credit_key = arg_get_lit(ctx, 6);
|
|
bool elite = arg_get_lit(ctx, 7);
|
|
bool rawkey = arg_get_lit(ctx, 8);
|
|
bool verbose = arg_get_lit(ctx, 9);
|
|
bool shallow_mod = arg_get_lit(ctx, 10);
|
|
bool use_replay = arg_get_lit(ctx, 11);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (rawkey + elite > 1) {
|
|
PrintAndLogEx(FAILED, "Can not use both 'e', 'r'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (startblock < 5) {
|
|
PrintAndLogEx(WARNING, "you cannot write key blocks this way. yet... make your start block > 4");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint32_t payload_size = sizeof(iclass_restore_req_t) + (sizeof(iclass_restore_item_t) * (endblock - startblock + 1));
|
|
|
|
if (payload_size > PM3_CMD_DATA_SIZE) {
|
|
PrintAndLogEx(NORMAL, "Trying to write too many blocks at once. Max: %d", PM3_CMD_DATA_SIZE / 8);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// read dump file
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 2048;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (bytes_read == 0) {
|
|
PrintAndLogEx(ERR, "file reading error");
|
|
free(dump);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
if (bytes_read < ((endblock - startblock + 1) * 8)) {
|
|
PrintAndLogEx(ERR, "file is smaller than your suggested block range ( " _RED_("0x%02x..0x%02x")" )",
|
|
startblock, endblock
|
|
);
|
|
free(dump);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
iclass_restore_req_t *payload = calloc(1, payload_size);
|
|
if (payload == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(dump);
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
payload->req.use_raw = rawkey;
|
|
payload->req.use_elite = elite;
|
|
payload->req.use_credit_key = use_credit_key;
|
|
payload->req.use_replay = use_replay;
|
|
payload->req.blockno = startblock;
|
|
payload->req.send_reply = true;
|
|
payload->req.do_auth = true;
|
|
payload->req.shallow_mod = shallow_mod;
|
|
memcpy(payload->req.key, key, 8);
|
|
|
|
payload->item_cnt = (endblock - startblock + 1);
|
|
|
|
// read data from file from block 6 --- 19
|
|
// we will use this struct [data 8 bytes][MAC 4 bytes] for each block calculate all mac number for each data
|
|
// then copy to usbcommand->asbytes;
|
|
// max is 32 - 6 = 28 block. 28 x 12 bytes gives 336 bytes
|
|
|
|
for (uint8_t i = 0; i < payload->item_cnt; i++) {
|
|
payload->blocks[i].blockno = startblock + i;
|
|
memcpy(payload->blocks[i].data, dump + (startblock * 8) + (i * 8), sizeof(payload->blocks[i].data));
|
|
}
|
|
|
|
free(dump);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Preparing to restore block range %02d..%02d", startblock, endblock);
|
|
|
|
PrintAndLogEx(INFO, "---------+----------------------");
|
|
PrintAndLogEx(INFO, " block# | data");
|
|
PrintAndLogEx(INFO, "---------+----------------------");
|
|
|
|
for (uint8_t i = 0; i < payload->item_cnt; i++) {
|
|
iclass_restore_item_t item = payload->blocks[i];
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | %s", item.blockno, item.blockno, sprint_hex_inrow(item.data, sizeof(item.data)));
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "restore started...");
|
|
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_RESTORE, (uint8_t *)payload, payload_size);
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_RESTORE, &resp, 2500) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
DropField();
|
|
free(payload);
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "iCLASS restore " _GREEN_("successful"));
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass rdbl") "` to verify data on card");
|
|
} else {
|
|
PrintAndLogEx(WARNING, "iCLASS restore " _RED_("failed"));
|
|
}
|
|
|
|
free(payload);
|
|
return resp.status;
|
|
}
|
|
|
|
static int iclass_read_block_ex(uint8_t *KEY, uint8_t blockno, uint8_t keyType, bool elite, bool rawkey, bool replay, bool verbose,
|
|
bool auth, bool shallow_mod, uint8_t *out, bool print, bool loop) {
|
|
|
|
iclass_auth_req_t payload = {
|
|
.use_raw = rawkey,
|
|
.use_elite = elite,
|
|
.use_credit_key = (keyType == 0x18),
|
|
.use_replay = replay,
|
|
.blockno = blockno,
|
|
.send_reply = true,
|
|
.do_auth = auth,
|
|
.shallow_mod = shallow_mod,
|
|
};
|
|
memcpy(payload.key, KEY, 8);
|
|
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_READBL, (uint8_t *)&payload, sizeof(payload));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READBL, &resp, 2000) == false) {
|
|
if (verbose) PrintAndLogEx(WARNING, "command execution time out");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
if (resp.status != PM3_SUCCESS) {
|
|
if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
|
|
return PM3_EWRONGANSWER;
|
|
}
|
|
|
|
// return data.
|
|
iclass_readblock_resp_t *packet = (iclass_readblock_resp_t *)resp.data.asBytes;
|
|
|
|
if (packet->isOK == false) {
|
|
if (verbose) PrintAndLogEx(FAILED, "authentication error");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (print) {
|
|
|
|
if (loop == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
PrintAndLogEx(SUCCESS, " block %3d/0x%02X : " _GREEN_("%s"), blockno, blockno, sprint_hex(packet->data, sizeof(packet->data)));
|
|
if (loop == false) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
}
|
|
|
|
if (out) {
|
|
memcpy(out, packet->data, sizeof(packet->data));
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int iclass_read_block(uint8_t *KEY, uint8_t blockno, uint8_t keyType, bool elite, bool rawkey, bool replay, bool verbose,
|
|
bool auth, bool shallow_mod, uint8_t *out, bool loop) {
|
|
return iclass_read_block_ex(KEY, blockno, keyType, elite, rawkey, replay, verbose, auth, shallow_mod, out, true, loop);
|
|
}
|
|
|
|
static int CmdHFiClass_ReadBlock(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass rdbl",
|
|
"Read a iCLASS block from tag",
|
|
"hf iclass rdbl --blk 6 -k 0011223344556677\n"
|
|
"hf iclass rdbl --blk 27 -k 0011223344556677 --credit\n"
|
|
"hf iclass rdbl --blk 10 --ki 0");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int1(NULL, "blk", "<dec>", "Block number"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_lit0(NULL, "nr", "replay of NR/MAC"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0("@", NULL, "optional - continuous mode"),
|
|
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;
|
|
}
|
|
|
|
bool auth = false;
|
|
|
|
if (key_len > 0) {
|
|
auth = true;
|
|
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) {
|
|
auth = true;
|
|
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;
|
|
}
|
|
}
|
|
|
|
int blockno = arg_get_int_def(ctx, 3, 0);
|
|
|
|
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
|
|
if (arg_get_lit(ctx, 4)) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
|
keyType = ICLASS_CREDIT_KEYTYPE;
|
|
}
|
|
|
|
bool elite = arg_get_lit(ctx, 5);
|
|
bool rawkey = arg_get_lit(ctx, 6);
|
|
bool use_replay = arg_get_lit(ctx, 7);
|
|
bool verbose = arg_get_lit(ctx, 8);
|
|
bool shallow_mod = arg_get_lit(ctx, 9);
|
|
bool cm = arg_get_lit(ctx, 10);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (verbose) {
|
|
if (key_len > 0)
|
|
PrintAndLogEx(SUCCESS, "Using key %s", sprint_hex(key, 8));
|
|
}
|
|
|
|
if (auth == false && verbose) {
|
|
PrintAndLogEx(WARNING, "warning: no authentication used with read. Typical for cards configured into `non-secure page`");
|
|
|
|
}
|
|
|
|
int res = 0;
|
|
uint8_t data[8] = {0};
|
|
|
|
if (cm) {
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
|
}
|
|
|
|
do {
|
|
memset(data, 0, sizeof(data));
|
|
|
|
res = iclass_read_block(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, data, cm);
|
|
if (!cm && res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
} while (cm && (kbd_enter_pressed() == false));
|
|
|
|
|
|
if (blockno < 6 || blockno > 7)
|
|
return PM3_SUCCESS;
|
|
|
|
if (memcmp(data, empty, 8) == 0)
|
|
return PM3_SUCCESS;
|
|
|
|
bool use_sc = IsCardHelperPresent(verbose);
|
|
if (use_sc == false) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// crypto helper available.
|
|
PrintAndLogEx(INFO, "----------------------------- " _CYAN_("Cardhelper") " -----------------------------");
|
|
|
|
switch (blockno) {
|
|
case 6: {
|
|
DecodeBlock6(data);
|
|
break;
|
|
}
|
|
case 7: {
|
|
|
|
uint8_t dec_data[PICOPASS_BLOCK_SIZE];
|
|
|
|
uint64_t a = bytes_to_num(data, PICOPASS_BLOCK_SIZE);
|
|
bool starts = (leadingzeros(a) < 12);
|
|
bool ones = (bitcount64(a) > 16 && bitcount64(a) < 48);
|
|
|
|
if (starts && ones) {
|
|
PrintAndLogEx(INFO, "data looks encrypted, False Positives " _YELLOW_("ARE") " possible");
|
|
Decrypt(data, dec_data);
|
|
PrintAndLogEx(SUCCESS, "decrypted : " _GREEN_("%s"), sprint_hex(dec_data, sizeof(dec_data)));
|
|
} else {
|
|
memcpy(dec_data, data, sizeof(dec_data));
|
|
PrintAndLogEx(INFO, "data looks unencrypted, trying to decode");
|
|
}
|
|
|
|
bool has_new_pacs = iclass_detect_new_pacs(dec_data);
|
|
bool has_values = (memcmp(dec_data, empty, PICOPASS_BLOCK_SIZE) != 0) && (memcmp(dec_data, zeros, PICOPASS_BLOCK_SIZE) != 0);
|
|
|
|
if (has_values) {
|
|
|
|
if (has_new_pacs) {
|
|
iclass_decode_credentials_new_pacs(dec_data);
|
|
} else {
|
|
//todo: remove preamble/sentinel
|
|
uint32_t top = 0, mid = 0, bot = 0;
|
|
|
|
char hexstr[16 + 1] = {0};
|
|
hex_to_buffer((uint8_t *)hexstr, dec_data, PICOPASS_BLOCK_SIZE, sizeof(hexstr) - 1, 0, 0, true);
|
|
hexstring_to_u96(&top, &mid, &bot, hexstr);
|
|
|
|
char binstr[64 + 1];
|
|
hextobinstring(binstr, hexstr);
|
|
char *pbin = binstr;
|
|
// Strip leading zeros
|
|
while (strlen(pbin) && *(++pbin) == '0');
|
|
|
|
size_t binlen = strlen(pbin);
|
|
|
|
// Check if we have a sentinel bit (leading '1' that makes length one more than common formats)
|
|
// Common formats: 26, 30, 33, 34, 35, 36, 37, 46, 48
|
|
// If we have 27, 31, 34, 35, 36, 37, 38, 47, 49 bits and it starts with '1',
|
|
// it's likely a sentinel bit that should be stripped
|
|
if (binlen > 0 && pbin[0] == '1' &&
|
|
(binlen == 27 || binlen == 31 || binlen == 34 || binlen == 35 ||
|
|
binlen == 36 || binlen == 37 || binlen == 38 || binlen == 47 || binlen == 49)) {
|
|
// Strip the sentinel bit by recreating u96 from binary string without leading '1'
|
|
char *corrected_bin = pbin + 1; // Skip the leading '1'
|
|
size_t corrected_len = strlen(corrected_bin);
|
|
|
|
// Recreate u96 values from corrected binary string
|
|
top = 0;
|
|
mid = 0;
|
|
bot = 0;
|
|
binstring_to_u96(&top, &mid, &bot, corrected_bin);
|
|
|
|
pbin = corrected_bin;
|
|
binlen = corrected_len;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " bin : %s", pbin);
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "------------------------------ " _CYAN_("Wiegand") " -------------------------------");
|
|
// Use the corrected length (without sentinel) for decoding
|
|
decode_wiegand(top, mid, bot, (int)binlen);
|
|
}
|
|
} else {
|
|
PrintAndLogEx(INFO, "no credential found");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
PrintAndLogEx(INFO, "----------------------------------------------------------------------");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
|
|
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) {
|
|
snprintf(line1 + l1, sizeof(line1) - l1, _RED_("%1X"), hi1);
|
|
snprintf(line2 + l2, sizeof(line2) - l2, _GREEN_("%1X"), hi2);
|
|
} else {
|
|
snprintf(line1 + l1, sizeof(line1) - l1, "%1X", hi1);
|
|
snprintf(line2 + l2, sizeof(line2) - l2, "%1X", hi2);
|
|
}
|
|
|
|
l1 = strlen(line1);
|
|
l2 = strlen(line2);
|
|
|
|
if (low1 != low2) {
|
|
snprintf(line1 + l1, sizeof(line1) - l1, _RED_("%1X"), low1);
|
|
snprintf(line2 + l2, sizeof(line2) - l2, _GREEN_("%1X"), low2);
|
|
} else {
|
|
snprintf(line1 + l1, sizeof(line1) - l1, "%1X", low1);
|
|
snprintf(line2 + l2, sizeof(line2) - l2, "%1X", low2);
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "%s", line1);
|
|
PrintAndLogEx(INFO, "%s", line2);
|
|
}
|
|
|
|
static int CmdHFiClass_TearBlock(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass tear",
|
|
"Tear off an iCLASS tag block\n"
|
|
"e-purse usually 300-500us to trigger the erase phase\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 --ki 0 -s 300 -e 600\n"
|
|
"hf iclass tear --blk 2 -d fdffffffffffffff --ki 1 --credit -s 400 -e 500"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int1(NULL, "blk", "<dec>", "block number"),
|
|
arg_str0("d", "data", "<hex>", "data to write as 8 hex bytes"),
|
|
arg_str0("m", "mac", "<hex>", "replay mac data (4 hex bytes)"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_lit0(NULL, "nr", "replay of NR/MAC"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_int1("s", NULL, "<dec>", "tearoff delay start (in us) must be between 1 and 43000 (43ms). Precision is about 1/3 us"),
|
|
arg_int0("i", NULL, "<dec>", "tearoff delay increment (in us) - default 10"),
|
|
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);
|
|
|
|
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);
|
|
int blockno = arg_get_int_def(ctx, 3, 0);
|
|
|
|
int data_len = 0;
|
|
uint8_t data[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, data, &data_len);
|
|
|
|
int mac_len = 0;
|
|
uint8_t mac[4] = {0};
|
|
CLIGetHexWithReturn(ctx, 5, mac, &mac_len);
|
|
|
|
bool use_credit_key = arg_get_lit(ctx, 6);
|
|
bool elite = arg_get_lit(ctx, 7);
|
|
bool rawkey = arg_get_lit(ctx, 8);
|
|
bool use_replay = arg_get_lit(ctx, 9);
|
|
bool verbose = arg_get_lit(ctx, 10);
|
|
bool shallow_mod = arg_get_lit(ctx, 11);
|
|
|
|
int tearoff_start = arg_get_int_def(ctx, 12, 100);
|
|
int tearoff_original_start = tearoff_start; // save original start value for later use
|
|
int tearoff_increment = arg_get_int_def(ctx, 13, 10);
|
|
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);
|
|
|
|
// Sanity checks
|
|
if (key_len > 0 && key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify key or index, not both");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool auth = false;
|
|
|
|
if (key_len > 0) {
|
|
|
|
auth = true;
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
if (key_nr >= 0) {
|
|
if (key_nr < ICLASS_KEYS_MAX) {
|
|
auth = true;
|
|
memcpy(key, iClass_Key_Table[key_nr], 8);
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Using Key... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
}
|
|
|
|
if (data_len && data_len != 8) {
|
|
PrintAndLogEx(ERR, "Data must be 8 hex bytes (16 hex symbols), got " _RED_("%u"), data_len);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (mac_len && mac_len != 4) {
|
|
PrintAndLogEx(ERR, "MAC must be 4 hex bytes (8 hex symbols)");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (tearoff_end <= tearoff_start) {
|
|
PrintAndLogEx(ERR, "Tearoff end delay must be larger than the start delay");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (tearoff_start <= 0) {
|
|
PrintAndLogEx(ERR, "Tearoff_start delays must be larger than 0");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (tearoff_end <= 0) {
|
|
PrintAndLogEx(ERR, "Tearoff_end delays must be larger than 0");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of `--elite`, `--raw`, `--nr`");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int loop_count = 0;
|
|
int isok = PM3_SUCCESS;
|
|
bool read_ok = false;
|
|
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
|
|
if (use_credit_key) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
|
keyType = ICLASS_CREDIT_KEYTYPE;
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("debit") " key");
|
|
}
|
|
|
|
if (data_len && auth == false) {
|
|
PrintAndLogEx(SUCCESS, "No key supplied. Trying no authentication read/writes");
|
|
}
|
|
|
|
if (tearoff_loop > 1) {
|
|
PrintAndLogEx(SUCCESS, _YELLOW_("%u") " attempts / tearoff", tearoff_loop);
|
|
}
|
|
|
|
if (tearoff_sleep) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("%u") " ms delay between attempts", tearoff_sleep);
|
|
}
|
|
|
|
//check if the card is in secure mode or not
|
|
iclass_card_select_t payload_rdr = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support for tearblock yet
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload_rdr.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
if (resp.status == PM3_ERFTRANS) {
|
|
PrintAndLogEx(FAILED, "no tag found");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
if (r->status == FLAG_ICLASS_NULL) {
|
|
PrintAndLogEx(FAILED, "failed to read block 0,1,2");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
int fail_tolerance = 1;
|
|
if (memcmp(r->header.hdr.csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
|
|
PrintAndLogEx(SUCCESS, "New silicon detected ( %s )", _GREEN_("ok"));
|
|
PrintAndLogEx(INFO, "----------------------------------------");
|
|
PrintAndLogEx(SUCCESS, "CSN................... %s", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Old silicon detected ( %s )", _GREEN_("ok"));
|
|
PrintAndLogEx(INFO, "CSN... %s", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
|
|
fail_tolerance = 5;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(INFO, "Card in non-secure page mode detected");
|
|
auth = false;
|
|
}
|
|
|
|
if (pagemap == 0x0) {
|
|
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled"));
|
|
goto out;
|
|
}
|
|
|
|
|
|
// perform initial read here, repeat if failed or 00s
|
|
bool read_auth = auth;
|
|
uint8_t data_read_orig[8] = {0};
|
|
uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
bool first_read = false;
|
|
bool reread = false;
|
|
bool erase_phase = false;
|
|
|
|
if (blockno < 3) {
|
|
read_auth = false;
|
|
}
|
|
|
|
int res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false, false);
|
|
while (reread) {
|
|
if (res_orig == PM3_SUCCESS && !reread) {
|
|
if (memcmp(data_read_orig, zeros, 8) == 0) {
|
|
reread = true;
|
|
} else {
|
|
reread = false;
|
|
}
|
|
} else if (res_orig == PM3_SUCCESS && reread) {
|
|
reread = false;
|
|
if (blockno == 2 && memcmp(data_read_orig, zeros, 8) == 0) {
|
|
reread = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (blockno == 2 && data_len == 0) {
|
|
int value_index = 0; //assuming FFFFFFFF is on the right
|
|
if (memcmp(data_read_orig + 4, "\xFF\xFF\xFF\xFF", 4) != 0) { //FFFFFFFF is on the left
|
|
value_index = 4;
|
|
}
|
|
memcpy(key, iClass_Key_Table[1], PICOPASS_BLOCK_SIZE);
|
|
use_credit_key = true;
|
|
auth = true;
|
|
memcpy(data, data_read_orig, PICOPASS_BLOCK_SIZE);
|
|
//decrease the debit epurse value by 1
|
|
if (data_read_orig[value_index] != 0x00) {
|
|
data[value_index]--;
|
|
} else {
|
|
data[value_index + 2]--;
|
|
data[value_index] = 0xFF;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Original block data... " _CYAN_("%s"), sprint_hex_inrow(data_read_orig, sizeof(data_read_orig)));
|
|
PrintAndLogEx(SUCCESS, "New data to write..... " _YELLOW_("%s"), sprint_hex_inrow(data, sizeof(data)));
|
|
PrintAndLogEx(SUCCESS, "Target block.......... " _YELLOW_("%u") " / " _YELLOW_("0x%02x"), blockno, blockno);
|
|
|
|
// turn off Device side debug messages
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
// clear trace log
|
|
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
|
|
|
|
if (run_on_device) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "---------------------------------------");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n");
|
|
|
|
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));
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
clearCommandBuffer();
|
|
return isok;
|
|
|
|
} else {
|
|
|
|
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.");
|
|
isok = PM3_EOPABORTED;
|
|
goto out;
|
|
}
|
|
|
|
// set tear off trigger
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
// 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, 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 > fail_tolerance) {
|
|
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) {
|
|
|
|
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 {
|
|
|
|
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;
|
|
|
|
// if bit flipped, mark it with color
|
|
if (bit1 != bit2) {
|
|
PrintAndLogEx(SUCCESS, "%-11s %-10d " _GREEN_("%-10d"), flag_names[i], bit1, bit2);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
DropField();
|
|
|
|
if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
// disable tearoff in case of keyboard abort, or it'll trigger on next operation
|
|
clearCommandBuffer();
|
|
tearoff_params_t params = {
|
|
.delay_us = tearoff_start,
|
|
.on = false,
|
|
.off = true
|
|
};
|
|
handle_tearoff(¶ms, false);
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
clearCommandBuffer();
|
|
return isok;
|
|
}
|
|
|
|
static void iclass_read_interesting_data(uint8_t *key, uint8_t keyType, bool elite, bool rawkey, bool use_replay,
|
|
bool verbose, bool shallow_mod) {
|
|
bool auth = false;
|
|
uint8_t blockno = 3;
|
|
uint8_t kd_read[8] = {0};
|
|
iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, kd_read, false, false);
|
|
|
|
blockno = 4;
|
|
uint8_t kc_read[8] = {0};
|
|
iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, kc_read, false, false);
|
|
|
|
PrintAndLogEx(SUCCESS, "Raw Debit Key.............. " _YELLOW_("%s"), sprint_hex_inrow(kd_read, sizeof(kd_read)));
|
|
PrintAndLogEx(SUCCESS, "Raw Credit Key............. " _YELLOW_("%s"), sprint_hex_inrow(kc_read, sizeof(kc_read)));
|
|
|
|
// read dump data here and print (SIO , wiegand)
|
|
uint16_t n = 0;
|
|
uint8_t d[0x100 * PICOPASS_BLOCK_SIZE] = {0};
|
|
iclass_dump_non_secure(shallow_mod, d, &n);
|
|
|
|
DropField();
|
|
|
|
iclass_decode_credentials(d);
|
|
|
|
// We should send it to SAM if we detect one installed, and get the FC/CN out
|
|
}
|
|
|
|
static int CmdHFiClass_BlackTears(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass blacktears",
|
|
"Tear off the iCLASS (new-silicon only) configuration block to set non-secure page mode.\n"
|
|
"Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`\n",
|
|
"hf iclass blacktears -k 001122334455667B <-- debit custom key\n"
|
|
"hf iclass blacktears --credit --ki 1 <-- credit key at index 1"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_int0("s", NULL, "<dec>", "tearoff delay start (in us) must be between 1 and 43000 (43ms). Precision is about 1/3 us"),
|
|
arg_int0("i", NULL, "<dec>", "tearoff delay increment (in us) - default 5"),
|
|
arg_int0("e", NULL, "<dec>", "tearoff delay end (in us) must be a higher value than the start delay"),
|
|
arg_str0("o", "otp", "<hex>", "Custom OTP value as 2 hex bytes"),
|
|
arg_lit0(NULL, "dns", "Do not stabilize the bits, and return the raw dump of the block after tearoff"),
|
|
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);
|
|
bool use_credit_key = arg_get_lit(ctx, 3);
|
|
|
|
// Start at 1700, end at 1900.
|
|
// 5 steps increments
|
|
|
|
int tearoff_start = arg_get_int_def(ctx, 4, 1700);
|
|
int tearoff_original_start = tearoff_start; // save original start value for later use
|
|
int tearoff_increment = arg_get_int_def(ctx, 5, 5);
|
|
int tearoff_end = arg_get_int_def(ctx, 6, tearoff_start + 200); // 1900 default
|
|
|
|
int otp_len = 0;
|
|
uint8_t otp[2] = {0};
|
|
CLIGetHexWithReturn(ctx, 7, otp, &otp_len);
|
|
|
|
bool do_not_stabilize = arg_get_lit(ctx, 8);
|
|
bool rawkey = arg_get_lit(ctx, 9);
|
|
bool verbose = arg_get_lit(ctx, 10);
|
|
bool shallow_mod = arg_get_lit(ctx, 11);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
bool elite = false;
|
|
bool use_replay = false; // not implemented in this mode
|
|
bool read_auth = false;
|
|
|
|
int tearoff_loop = 1;
|
|
int tearoff_sleep = 0;
|
|
|
|
// Sanity checks
|
|
if (key_len > 0 && key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify key or index, not both");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool auth = false;
|
|
|
|
if (key_len) {
|
|
|
|
auth = true;
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
if (key_nr >= 0) {
|
|
if (key_nr < ICLASS_KEYS_MAX) {
|
|
auth = true;
|
|
memcpy(key, iClass_Key_Table[key_nr], 8);
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Using Key... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
}
|
|
|
|
if (otp_len && otp_len != 2) {
|
|
PrintAndLogEx(ERR, "OTP is incorrect length");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int loop_count = 0;
|
|
int isok = PM3_SUCCESS;
|
|
bool read_ok = false;
|
|
|
|
uint8_t keyType = ICLASS_DEBIT_KEYTYPE;
|
|
|
|
if (use_credit_key) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
|
keyType = ICLASS_CREDIT_KEYTYPE;
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("debit") " key");
|
|
}
|
|
|
|
if (tearoff_loop > 1) {
|
|
PrintAndLogEx(SUCCESS, _YELLOW_("%u") " attempts / tearoff", tearoff_loop);
|
|
}
|
|
|
|
if (tearoff_sleep) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("%u") " ms delay between attempts", tearoff_sleep);
|
|
}
|
|
|
|
//check if the card is in secure mode or not
|
|
iclass_card_select_t payload_rdr = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support for blacktears yet
|
|
};
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
PrintAndLogEx(WARNING, "command execution time out");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
if (resp.status == PM3_ERFTRANS) {
|
|
PrintAndLogEx(FAILED, "no tag found");
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
if (r->status == FLAG_ICLASS_NULL) {
|
|
PrintAndLogEx(FAILED, "failed to read block 0,1,2");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
int fail_tolerance = 1;
|
|
if (memcmp(r->header.hdr.csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
|
|
PrintAndLogEx(SUCCESS, "New silicon detected ( %s )", _GREEN_("ok"));
|
|
PrintAndLogEx(INFO, "---------------------------------------");
|
|
PrintAndLogEx(SUCCESS, "CSN................... %s", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Old silicon detected ( %s )", _RED_("fail"));
|
|
PrintAndLogEx(INFO, "Unsupported for this operation");
|
|
PrintAndLogEx(INFO, "CSN... %s", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE));
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(INFO, "Card already in non-secure page mode");
|
|
PrintAndLogEx(HINT, "try `" _YELLOW_("hf iclass dump") "` - to extract all memory");
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (pagemap == 0x0) {
|
|
PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if " _YELLOW_("RA") " is enabled"));
|
|
goto out;
|
|
}
|
|
|
|
#define TEAR_NON_SECURE_MODE 0x2C
|
|
#define TEAR_INITAL 0x3C
|
|
#define TEAR_PERSO 0xAC
|
|
|
|
#define TEAR_IS_PERSO_SET(x) (((x) & 0x80) == 0x80)
|
|
#define TEAR_IS_NONSECURE_SET(x) ((((x) & 0x18) >> 3) == PICOPASS_NON_SECURE_PAGEMODE)
|
|
#define TEAR_BAD(x) (((x) & 0x01) == 0)
|
|
|
|
// perform initial read here, repeat if failed or 00s
|
|
|
|
uint8_t data_read_orig[8] = {0};
|
|
uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
bool first_read = false;
|
|
bool erase_phase = false;
|
|
|
|
read_auth = false;
|
|
|
|
int blockno = 1;
|
|
|
|
int res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false, false);
|
|
if (res_orig == PM3_SUCCESS && memcmp(data_read_orig, zeros, 8) == 0) {
|
|
// zeros may be a transient read artifact - read once more to confirm
|
|
res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false, false);
|
|
}
|
|
|
|
uint8_t data[8] = { 0 }; // tearoff payload
|
|
memcpy(data, data_read_orig, sizeof(data));
|
|
data[7] = TEAR_NON_SECURE_MODE;
|
|
|
|
// add the modified OTP if needed
|
|
if (otp_len) {
|
|
memcpy(data + 1, otp, sizeof(otp)); // update the otp in the tearoff data value
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "Original block data... " _CYAN_("%s"), sprint_hex_inrow(data_read_orig, sizeof(data_read_orig)));
|
|
PrintAndLogEx(SUCCESS, "New data to write..... " _YELLOW_("%s"), sprint_hex_inrow(data, sizeof(data)));
|
|
PrintAndLogEx(SUCCESS, "Target block.......... " _YELLOW_("%u") " / " _YELLOW_("0x%02x"), blockno, blockno);
|
|
|
|
// turn off Device side debug messages
|
|
uint8_t dbg_curr = DBG_NONE;
|
|
if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
// 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
|
|
uint8_t mac[4] = {0};
|
|
|
|
while ((tearoff_start <= tearoff_end) && (read_ok == false)) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
|
isok = PM3_EOPABORTED;
|
|
goto out;
|
|
}
|
|
|
|
// set tear off trigger
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
// 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;
|
|
bool 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 block
|
|
read_auth = false;
|
|
|
|
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false, 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;
|
|
if (readcount == 100) {
|
|
PrintAndLogEx(WARNING, "\nCard not responding after %d attempts, press " _GREEN_("<Enter>") " to abort", readcount);
|
|
}
|
|
}
|
|
|
|
readcount++;
|
|
}
|
|
|
|
if (readcount > fail_tolerance) {
|
|
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) {
|
|
|
|
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 {
|
|
|
|
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;
|
|
|
|
// App limit became SMALLER :(
|
|
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_("0x%02X")" to "_YELLOW_("0x%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;
|
|
|
|
// if bit flipped, mark it with color
|
|
if (bit1 != bit2) {
|
|
PrintAndLogEx(SUCCESS, "%-11s %-10d " _GREEN_("%-10d"), flag_names[i], bit1, bit2);
|
|
} else {
|
|
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 ( %s )", _RED_("fail"));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
out:
|
|
|
|
DropField();
|
|
|
|
if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) {
|
|
return PM3_EFAILED;
|
|
}
|
|
// disable tearoff in case of keyboard abort, or it'll trigger on next operation
|
|
clearCommandBuffer();
|
|
tearoff_params_t params = {
|
|
.delay_us = tearoff_start,
|
|
.on = false,
|
|
.off = true
|
|
};
|
|
handle_tearoff(¶ms, false);
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
clearCommandBuffer();
|
|
|
|
read_auth = false;
|
|
uint8_t data_read[8] = {0};
|
|
|
|
int res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false, false);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
uint8_t b7 = data_read[7];
|
|
|
|
if (b7 == TEAR_INITAL) {
|
|
PrintAndLogEx(INFO, _YELLOW_("Fuses unchanged. Try again if the OTP is unchanged"));
|
|
// check for OTP change?
|
|
} else if (TEAR_IS_NONSECURE_SET(b7)) {
|
|
// don't do anything as this is ok
|
|
PrintAndLogEx(SUCCESS, "Detected fuse: " _GREEN_("0x%02X")" _non secure memory_ ( %s )", data_read[7], _GREEN_("ok"));
|
|
|
|
iclass_read_interesting_data(key, keyType, elite, rawkey, use_replay, verbose, shallow_mod);
|
|
} else if (TEAR_IS_PERSO_SET(b7)) {
|
|
|
|
PrintAndLogEx(SUCCESS, "Detected fuse: " _GREEN_("0x%02X") " set non-secure memory: " _YELLOW_("0xAC"), data_read[7]);
|
|
|
|
memcpy(data, data_read, PICOPASS_BLOCK_SIZE);
|
|
data[7] = TEAR_NON_SECURE_MODE;
|
|
|
|
// set non-secure memory with 0xAC, in this state it will always succeed
|
|
if (do_not_stabilize) {
|
|
PrintAndLogEx(INFO, "Not stabilizing the bits...");
|
|
} else {
|
|
PrintAndLogEx(INFO, "Stabilizing the bits...");
|
|
iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod);
|
|
}
|
|
|
|
iclass_read_interesting_data(key, keyType, elite, rawkey, use_replay, verbose, shallow_mod);
|
|
} else if (TEAR_BAD(b7)) {
|
|
PrintAndLogEx(WARNING, "Bad fuse state detected: " _RED_("0x%02X") ", cannot proceed", b7);
|
|
return PM3_EFAILED;
|
|
} else {
|
|
PrintAndLogEx(INFO, _YELLOW_("Did not detect " _YELLOW_("0xBC") " or " _YELLOW_("0xBE") " or " _YELLOW_("0x9C") " fuse, might need manual intervention!"));
|
|
}
|
|
|
|
return isok;
|
|
}
|
|
|
|
static int CmdHFiClass_loclass(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass loclass",
|
|
"Execute the offline part of loclass attack\n"
|
|
" An iclass dumpfile is assumed to consist of an arbitrary number of\n"
|
|
" malicious CSNs, and their protocol responses\n"
|
|
" The binary format of the file is expected to be as follows: \n"
|
|
" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
|
|
" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
|
|
" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
|
|
" ... totalling N*24 bytes",
|
|
"hf iclass loclass -f iclass_dump.bin\n"
|
|
"hf iclass loclass --test");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "filename with nr/mac data from `hf iclass sim -t 2` "),
|
|
arg_lit0(NULL, "test", "Perform self test"),
|
|
arg_lit0(NULL, "long", "Perform self test, including long ones"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
bool test = arg_get_lit(ctx, 2);
|
|
bool longtest = arg_get_lit(ctx, 3);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (test || longtest) {
|
|
int errors = testCipherUtils();
|
|
errors += testMAC();
|
|
errors += doKeyTests();
|
|
errors += testElite(longtest);
|
|
|
|
if (errors != PM3_SUCCESS)
|
|
PrintAndLogEx(ERR, "There were errors!!!");
|
|
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
return bruteforceFileNoKeys(filename);
|
|
}
|
|
|
|
static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_legacy, bool *is_se, bool *is_sr, uint8_t **sio_start_ptr, size_t *sio_length) {
|
|
*is_legacy = false;
|
|
*is_sr = false;
|
|
*is_se = false;
|
|
if (sio_start_ptr != NULL) {
|
|
*sio_start_ptr = NULL;
|
|
}
|
|
if (sio_length != NULL) {
|
|
*sio_length = 0;
|
|
}
|
|
|
|
if (dump_len < sizeof(picopass_hdr_t)) {
|
|
// Can't really do anything with a dump that doesn't include the header
|
|
return;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump;
|
|
|
|
if (memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE) == 0) {
|
|
// Legacy AIA
|
|
*is_legacy = true;
|
|
|
|
if (dump_len < 11 * PICOPASS_BLOCK_SIZE) {
|
|
// Can't reliably detect if the card is SR without checking
|
|
// blocks 6 and 10
|
|
return;
|
|
}
|
|
|
|
// SR bit set in legacy config block
|
|
if ((iclass_dump[6 * PICOPASS_BLOCK_SIZE] & ICLASS_CFG_BLK_SR_BIT) == ICLASS_CFG_BLK_SR_BIT) {
|
|
// If the card is blank (all FF's) then we'll reach here too, so check for an empty block 10
|
|
// to avoid false positivies
|
|
if (memcmp(iclass_dump + (10 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
|
|
*is_sr = true;
|
|
if (sio_start_ptr != NULL) {
|
|
// SR SIO starts at block 10
|
|
*sio_start_ptr = iclass_dump + (10 * PICOPASS_BLOCK_SIZE);
|
|
}
|
|
}
|
|
}
|
|
} else if (memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE) == 0) {
|
|
// SE AIA
|
|
*is_se = true;
|
|
|
|
if (sio_start_ptr != NULL) {
|
|
// SE SIO starts at block 6
|
|
*sio_start_ptr = iclass_dump + (6 * PICOPASS_BLOCK_SIZE);
|
|
}
|
|
}
|
|
|
|
if (sio_length == NULL || sio_start_ptr == NULL || *sio_start_ptr == NULL) {
|
|
// No need to calculate length
|
|
return;
|
|
}
|
|
|
|
uint8_t *sio_start = *sio_start_ptr;
|
|
|
|
if (sio_start[0] != 0x30) {
|
|
// SIOs always start with a SEQUENCE(P), if this is missing then bail
|
|
return;
|
|
}
|
|
|
|
if (sio_start[1] >= 0x80 || sio_start[1] == 0x00) {
|
|
// We only support definite short form lengths
|
|
return;
|
|
}
|
|
|
|
// Length of bytes within the SEQUENCE, plus tag and length bytes for the SEQUENCE tag
|
|
*sio_length = sio_start[1] + 2;
|
|
}
|
|
|
|
static void print_iclass_sio_decoded(uint8_t *sio, size_t sio_len) {
|
|
// Outer SEQUENCE already validated by detect_credential (short-form length, 0x30 tag)
|
|
if (sio_len < 4 || sio[0] != 0x30) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *p = sio + 2;
|
|
uint8_t *end = sio + sio_len;
|
|
|
|
PrintAndLogEx(INFO, "-------------------- " _CYAN_("SIO - Insights") " -------------------------");
|
|
|
|
while (p + 2 <= end) {
|
|
uint8_t tag = *p++;
|
|
uint8_t len = *p++;
|
|
if (p + len > end) {
|
|
break;
|
|
}
|
|
uint8_t *val = p;
|
|
p += len;
|
|
|
|
switch (tag) {
|
|
case 0x81: {
|
|
// [1] IMPLICIT - Credential Template OID
|
|
// For iClass SE / Seos, the first byte is a preamble (0x01) and the actual OID is the remaining 4 bytes
|
|
if (len == 5 && val[0] == 0x01) {
|
|
PrintAndLogEx(INFO, " Relative OID : " _YELLOW_("%s") " ( Detected preamble: %02X - SE/SEOS )", sprint_hex_inrow(val, 5), val[0]);
|
|
} else {
|
|
PrintAndLogEx(INFO, " Relative OID : " _YELLOW_("%s"), sprint_hex_inrow(val, len));
|
|
}
|
|
break;
|
|
}
|
|
case 0x83: {
|
|
// [3] IMPLICIT - Tail present in SR and custom-keyed SE credentials
|
|
PrintAndLogEx(INFO, " OID Tail : " _YELLOW_("%s")" (SR / Custom-Keyed SE)", sprint_hex_inrow(val, len));
|
|
break;
|
|
}
|
|
case 0xA6: {
|
|
// [6] CONSTRUCTED - Key Info
|
|
uint8_t *ip = val;
|
|
uint8_t *ie = val + len;
|
|
while (ip + 2 <= ie) {
|
|
uint8_t itag = *ip++;
|
|
uint8_t ilen = *ip++;
|
|
if (ip + ilen > ie) break;
|
|
uint8_t *ival = ip;
|
|
ip += ilen;
|
|
if (itag == 0x80) {
|
|
// Custom Key OID (used instead of Key Reference ID for custom-keyed credentials)
|
|
PrintAndLogEx(INFO, " Custom Key OID : " _YELLOW_("%s"), sprint_hex_inrow(ival, ilen));
|
|
} else if (itag == 0x81 && ilen == 1) {
|
|
const char *ktype;
|
|
switch (ival[0]) {
|
|
case 0x00:
|
|
ktype = "Elite keyed";
|
|
break;
|
|
case 0x01:
|
|
ktype = "Standard keyed";
|
|
break;
|
|
default:
|
|
ktype = "Custom keyed";
|
|
break;
|
|
}
|
|
PrintAndLogEx(INFO, " Key Reference ID : " _YELLOW_("0x%02X") " ( %s )", ival[0], ktype);
|
|
} else if (itag == 0x04 && ilen >= 1) {
|
|
uint8_t ctype = ival[ilen - 1];
|
|
const char *cname;
|
|
switch (ctype) {
|
|
case 0x08:
|
|
cname = "EAX";
|
|
break;
|
|
case 0x09:
|
|
cname = "EAX'";
|
|
break;
|
|
default:
|
|
cname = "Unknown";
|
|
break;
|
|
}
|
|
PrintAndLogEx(INFO, " Crypto Suite : " _YELLOW_("%s") " ( %s )", sprint_hex_inrow(ival, ilen), cname);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 0xA7: {
|
|
// [7] CONSTRUCTED - PACS Encrypted Payload
|
|
uint8_t *ip = val;
|
|
uint8_t *ie = val + len;
|
|
while (ip + 2 <= ie) {
|
|
uint8_t itag = *ip++;
|
|
uint8_t ilen = *ip++;
|
|
if (ip + ilen > ie) break;
|
|
uint8_t *ival = ip;
|
|
ip += ilen;
|
|
if (itag == 0x85) {
|
|
if (ilen > 16) {
|
|
size_t data_len = ilen - 16;
|
|
PrintAndLogEx(INFO, " PACS Encrypted Data: " _YELLOW_("%s"), sprint_hex_inrow(ival, data_len));
|
|
PrintAndLogEx(INFO, " PACS MAC Signature : " _YELLOW_("%s"), sprint_hex_inrow(ival + data_len, 16));
|
|
} else if (ilen > 0) {
|
|
PrintAndLogEx(INFO, " PACS Payload : " _YELLOW_("%s"), sprint_hex_inrow(ival, ilen));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// print SIO decoded fields and optionally raw ASN1 TLV
|
|
void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len, bool verbose) {
|
|
|
|
bool is_legacy, is_se, is_sr;
|
|
uint8_t *sio_start;
|
|
size_t sio_length;
|
|
detect_credential(iclass_dump, dump_len, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);
|
|
|
|
// sanity checks
|
|
if (sio_start == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (dump_len < sio_length + (sio_start - iclass_dump)) {
|
|
// SIO length exceeds the size of the dump
|
|
return;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--------------------------- " _CYAN_("SIO - RAW") " -----------------------------");
|
|
print_hex_noascii_break(sio_start, sio_length, 32);
|
|
PrintAndLogEx(NORMAL, "");
|
|
print_iclass_sio_decoded(sio_start, sio_length);
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "----------------------- " _CYAN_("SIO - ASN1 TLV") " ---------------------------");
|
|
asn1_print(sio_start, sio_length, " ");
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
}
|
|
|
|
void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t endblock, size_t filesize, bool dense_output) {
|
|
|
|
picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump;
|
|
// picopass_ns_hdr_t *ns_hdr = (picopass_ns_hdr_t *)iclass_dump;
|
|
// uint8_t pagemap = get_pagemap(hdr);
|
|
// if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { }
|
|
|
|
uint8_t lock = hdr->conf.block_writelock;
|
|
|
|
// is chip in ReadOnly (RO)
|
|
bool ro = ((lock & 0x80) == 0);
|
|
|
|
uint8_t maxmemcount;
|
|
uint8_t filemaxblock = filesize / 8;
|
|
uint8_t mem_config = iclass_dump[13];
|
|
|
|
if (mem_config & 0x80)
|
|
maxmemcount = 255;
|
|
else
|
|
maxmemcount = 31;
|
|
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
|
|
if (startblock == 0) {
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
startblock = 3;
|
|
} else {
|
|
startblock = 6;
|
|
}
|
|
}
|
|
|
|
if ((endblock > maxmemcount) || (endblock == 0))
|
|
endblock = maxmemcount;
|
|
|
|
// remember endblock needs to relate to zero-index arrays.
|
|
if (endblock > filemaxblock - 1)
|
|
endblock = filemaxblock - 1;
|
|
|
|
/*
|
|
PrintAndLogEx(INFO, "startblock: %u, endblock: %u, filesize: %zu, maxmemcount: %u, filemaxblock: %u"
|
|
, startblock
|
|
, endblock
|
|
, filesize
|
|
, maxmemcount
|
|
, filemaxblock
|
|
);
|
|
*/
|
|
|
|
bool is_legacy, is_se, is_sr;
|
|
uint8_t *sio_start;
|
|
size_t sio_length;
|
|
detect_credential(iclass_dump, endblock * 8, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);
|
|
|
|
bool is_legacy_decrypted = is_legacy && (iclass_dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03) == 0x00;
|
|
|
|
int sio_start_block = 0, sio_end_block = 0;
|
|
if (sio_start && sio_length > 0) {
|
|
sio_start_block = (sio_start - iclass_dump) / PICOPASS_BLOCK_SIZE;
|
|
sio_end_block = sio_start_block + ((sio_length + PICOPASS_BLOCK_SIZE - 1) / PICOPASS_BLOCK_SIZE) - 1;
|
|
}
|
|
|
|
int i = startblock;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--------------------------- " _CYAN_("Tag memory") " ----------------------------");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, " block# | data | ascii |lck| info");
|
|
PrintAndLogEx(INFO, "---------+-------------------------+----------+---+----------------");
|
|
PrintAndLogEx(INFO, " 0/0x00 | " _GREEN_("%s") "| " _GREEN_("%s") " | | CSN "
|
|
, sprint_hex(iclass_dump, 8)
|
|
, sprint_ascii(iclass_dump, 8)
|
|
);
|
|
|
|
if (i != 1)
|
|
PrintAndLogEx(INFO, " ......");
|
|
|
|
bool in_repeated_block = false;
|
|
while (i <= endblock) {
|
|
uint8_t *blk = iclass_dump + (i * 8);
|
|
|
|
bool bl_lock = false;
|
|
if (ro == false) {
|
|
switch (i) {
|
|
case 12: {
|
|
bl_lock = ((lock & 0x40) == 0);
|
|
break;
|
|
}
|
|
case 11: {
|
|
bl_lock = ((lock & 0x20) == 0);
|
|
break;
|
|
}
|
|
case 10: {
|
|
bl_lock = ((lock & 0x10) == 0);
|
|
break;
|
|
}
|
|
case 9: {
|
|
bl_lock = ((lock & 0x08) == 0);
|
|
break;
|
|
}
|
|
case 8: {
|
|
bl_lock = ((lock & 0x04) == 0);
|
|
break;
|
|
}
|
|
case 7: {
|
|
bl_lock = ((lock & 0x02) == 0);
|
|
break;
|
|
}
|
|
case 6: {
|
|
bl_lock = ((lock & 0x01) == 0);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
bl_lock = true;
|
|
}
|
|
|
|
const char *lockstr = (bl_lock) ? _RED_("x") : " ";
|
|
|
|
const char *block_info;
|
|
bool regular_print_block = false;
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
const char *info_nonks[] = {"CSN", "Config", "AIA", "User"};
|
|
if (i < 3) {
|
|
block_info = info_nonks[i];
|
|
} else {
|
|
block_info = info_nonks[3];
|
|
}
|
|
|
|
regular_print_block = true;
|
|
} else {
|
|
const char *info_ks[] = {"CSN", "Config", "E-purse", "Debit", "Credit", "AIA", "User", "User AA2"};
|
|
|
|
if (i >= 6 && i <= 9 && is_legacy) {
|
|
// legacy credential
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | " _YELLOW_("%s") "| " _YELLOW_("%s") " | %s | User / %s "
|
|
, i
|
|
, i
|
|
, sprint_hex(blk, 8)
|
|
, sprint_ascii(blk, 8)
|
|
, lockstr
|
|
, i == 6 ? "HID CFG" : (is_legacy_decrypted ? "Cred" : "Enc Cred")
|
|
);
|
|
} else if (sio_start_block != 0 && i >= sio_start_block && i <= sio_end_block) {
|
|
// SIO credential
|
|
PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s") "| " _CYAN_("%s") " | %s | User / SIO / %s"
|
|
, i
|
|
, i
|
|
, sprint_hex(blk, 8)
|
|
, sprint_ascii(blk, 8)
|
|
, lockstr
|
|
, is_se ? "SE" : "SR"
|
|
);
|
|
} else {
|
|
if (i < 6) {
|
|
block_info = info_ks[i];
|
|
} else if (i > hdr->conf.app_limit) {
|
|
block_info = info_ks[7];
|
|
} else {
|
|
block_info = info_ks[6];
|
|
}
|
|
|
|
regular_print_block = true;
|
|
}
|
|
}
|
|
|
|
if (regular_print_block) {
|
|
// suppress repeating blocks, truncate as such that the first and last block with the same data is shown
|
|
// but the blocks in between are replaced with a single line of "......" if dense_output is enabled
|
|
if (dense_output && i > 6 && i < (endblock - 1) && !in_repeated_block && !memcmp(blk, blk - 8, 8) &&
|
|
!memcmp(blk, blk + 8, 8) && !memcmp(blk, blk + 16, 8)) {
|
|
// we're in a user block that isn't the first user block nor last two user blocks,
|
|
// and the current block data is the same as the previous and next two block
|
|
in_repeated_block = true;
|
|
PrintAndLogEx(INFO, " ......");
|
|
} else if (in_repeated_block && (memcmp(blk, blk + 8, 8) || i == endblock)) {
|
|
// in a repeating block, but the next block doesn't match anymore, or we're at the end block
|
|
in_repeated_block = false;
|
|
}
|
|
|
|
if (in_repeated_block == false) {
|
|
PrintAndLogEx(INFO,
|
|
"%3d/0x%02X | %s | %s | %s",
|
|
i,
|
|
i,
|
|
sprint_hex_ascii(blk, 8),
|
|
lockstr,
|
|
block_info);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
PrintAndLogEx(INFO, "---------+-------------------------+----------+---+----------------");
|
|
if (is_legacy)
|
|
PrintAndLogEx(HINT, _YELLOW_("yellow") " = legacy credential");
|
|
|
|
if (is_se) {
|
|
PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SE credential");
|
|
}
|
|
|
|
if (is_sr)
|
|
PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SR credential");
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
|
|
static int CmdHFiClassView(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass view",
|
|
"Print a iCLASS tag dump file (bin/eml/json)",
|
|
"hf iclass view -f hf-iclass-AA162D30F8FF12F1-dump.bin\n"
|
|
"hf iclass view --first 1 -f hf-iclass-AA162D30F8FF12F1-dump.bin\n\n"
|
|
"If --first is not specified it will default to the first user block\n"
|
|
"which is block 6 for secured chips or block 3 for non-secured chips");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
|
|
arg_int0(NULL, "first", "<dec>", "Begin printing from this block (default first user block)"),
|
|
arg_int0(NULL, "last", "<dec>", "End printing at this block (default 0, ALL)"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0("z", "dense", "dense dump output style"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE];
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int startblock = arg_get_int_def(ctx, 2, 0);
|
|
int endblock = arg_get_int_def(ctx, 3, 0);
|
|
bool verbose = arg_get_lit(ctx, 4);
|
|
bool dense_output = g_session.dense_output || arg_get_lit(ctx, 5);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
// read dump file
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 2048;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "File size %zu bytes, file blocks %d (0x%x)", bytes_read, (uint16_t)(bytes_read >> 3), (uint16_t)(bytes_read >> 3));
|
|
PrintAndLogEx(INFO, "Printing blocks from: " _YELLOW_("%02d") " to: " _YELLOW_("%02d"), (startblock == 0) ? 6 : startblock, endblock);
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
print_picopass_header((picopass_hdr_t *) dump);
|
|
print_picopass_info((picopass_hdr_t *) dump);
|
|
printIclassDumpContents(dump, startblock, endblock, bytes_read, dense_output);
|
|
iclass_decode_credentials(dump);
|
|
print_iclass_sio(dump, bytes_read, verbose);
|
|
|
|
free(dump);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
void HFiClassCalcDivKey(uint8_t *CSN, uint8_t *KEY, uint8_t *div_key, bool elite) {
|
|
if (elite) {
|
|
uint8_t keytable[128] = {0};
|
|
uint8_t key_index[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t key_sel[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t key_sel_p[PICOPASS_BLOCK_SIZE] = {0};
|
|
hash2(KEY, keytable);
|
|
hash1(CSN, key_index);
|
|
for (uint8_t i = 0; i < 8 ; i++) {
|
|
key_sel[i] = keytable[key_index[i]];
|
|
}
|
|
|
|
//Permute from iclass format to standard format
|
|
permutekey_rev(key_sel, key_sel_p);
|
|
diversifyKey(CSN, key_sel_p, div_key);
|
|
} else {
|
|
diversifyKey(CSN, KEY, div_key);
|
|
}
|
|
}
|
|
|
|
//when told CSN, oldkey, newkey, if new key is elite (elite), and if old key was elite (oldElite)
|
|
//calculate and return xor_div_key (ready for a key write command)
|
|
//print all div_keys if verbose
|
|
static void HFiClassCalcNewKey(uint8_t *CSN, uint8_t *OLDKEY, uint8_t *NEWKEY, uint8_t *xor_div_key, bool elite, bool oldElite, bool verbose) {
|
|
uint8_t old_div_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t new_div_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
//get old div key
|
|
HFiClassCalcDivKey(CSN, OLDKEY, old_div_key, oldElite);
|
|
//get new div key
|
|
HFiClassCalcDivKey(CSN, NEWKEY, new_div_key, elite);
|
|
|
|
for (uint8_t i = 0; i < ARRAYLEN(old_div_key); i++) {
|
|
xor_div_key[i] = old_div_key[i] ^ new_div_key[i];
|
|
}
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "Old div key.... %s", sprint_hex_inrow(old_div_key, PICOPASS_BLOCK_SIZE));
|
|
PrintAndLogEx(SUCCESS, "New div key.... " _MAGENTA_("%s"), sprint_hex_inrow(new_div_key, PICOPASS_BLOCK_SIZE));
|
|
PrintAndLogEx(SUCCESS, "Xor div key.... " _YELLOW_("%s") "\n", sprint_hex_inrow(xor_div_key, PICOPASS_BLOCK_SIZE));
|
|
}
|
|
}
|
|
|
|
static int CmdHFiClassCalcNewKey(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass calcnewkey",
|
|
"Calculate new keys for updating (blocks 3 & 4)",
|
|
"hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899 --csn deadbeafdeadbeaf --elite2 -> e key to e key given csn\n"
|
|
"hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899 --elite -> std key to e key read csn\n"
|
|
"hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899 -> std to std read csn");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "old", "<hex>", "Specify key as 8 hex bytes"),
|
|
arg_int0(NULL, "oki", "<dec>", "Old key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_str0(NULL, "new", "<hex>", "Specify key as 8 hex bytes"),
|
|
arg_int0(NULL, "nki", "<dec>", "New key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_str0(NULL, "csn", "<hex>", "Specify a Card Serial Number (CSN) to diversify the key (if omitted will attempt to read a CSN)"),
|
|
arg_lit0(NULL, "elite", "Elite computations applied to new key"),
|
|
arg_lit0(NULL, "elite2", "Elite computations applied to both old and new key"),
|
|
arg_lit0(NULL, "oldelite", "Elite computations applied only to old key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int old_key_len = 0;
|
|
uint8_t old_key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 1, old_key, &old_key_len);
|
|
|
|
int old_key_nr = arg_get_int_def(ctx, 2, -1);
|
|
|
|
if (old_key_len > 0 && old_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify old key or index, not both");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (old_key_len > 0) {
|
|
if (old_key_len != 8) {
|
|
PrintAndLogEx(ERR, "Old key is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else if (old_key_nr >= 0) {
|
|
if (old_key_nr < ICLASS_KEYS_MAX) {
|
|
memcpy(old_key, iClass_Key_Table[old_key_nr], 8);
|
|
PrintAndLogEx(SUCCESS, "Using old key[%d]... " _GREEN_("%s"), old_key_nr, sprint_hex(iClass_Key_Table[old_key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(ERR, "Please specify an old key or old key index");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int new_key_len = 0;
|
|
uint8_t new_key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, new_key, &new_key_len);
|
|
|
|
int new_key_nr = arg_get_int_def(ctx, 4, -1);
|
|
|
|
if (new_key_len > 0 && new_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify new key or index, not both");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (new_key_len > 0) {
|
|
if (new_key_len != 8) {
|
|
PrintAndLogEx(ERR, "New key is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else if (new_key_nr >= 0) {
|
|
if (new_key_nr < ICLASS_KEYS_MAX) {
|
|
memcpy(new_key, iClass_Key_Table[new_key_nr], 8);
|
|
PrintAndLogEx(SUCCESS, "Using new key[%d]... " _GREEN_("%s"), new_key_nr, sprint_hex(iClass_Key_Table[new_key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(ERR, "Please specify an new key or old key index");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int csn_len = 0;
|
|
uint8_t csn[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 5, csn, &csn_len);
|
|
bool givenCSN = false;
|
|
|
|
if (csn_len > 0) {
|
|
givenCSN = true;
|
|
if (csn_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
bool elite = arg_get_lit(ctx, 6);
|
|
bool old_elite = false;
|
|
|
|
if (arg_get_lit(ctx, 7)) {
|
|
elite = true;
|
|
old_elite = true;
|
|
}
|
|
|
|
if (arg_get_lit(ctx, 8)) {
|
|
elite = false;
|
|
old_elite = true;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t xor_div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (givenCSN == false) {
|
|
uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
if (select_only(csn, CCNR, true, false) == false) {
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
}
|
|
|
|
HFiClassCalcNewKey(csn, old_key, new_key, xor_div_key, elite, old_elite, true);
|
|
PrintAndLogEx(HINT, "Hint: Depending if card is in " _MAGENTA_("PERSONALIZATION") " or "_YELLOW_("APPLICATION") " mode");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int iclass_load_keys(char *filename) {
|
|
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 0;
|
|
if (loadFile_safe(filename, "", (void **)&dump, &bytes_read) != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
if (bytes_read > ICLASS_KEYS_MAX * PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(WARNING, "File is too long to load - bytes: %zu", bytes_read);
|
|
free(dump);
|
|
return PM3_EFILE;
|
|
}
|
|
size_t i = 0;
|
|
for (; i < bytes_read / PICOPASS_BLOCK_SIZE; i++) {
|
|
memcpy(iClass_Key_Table[i], dump + (i * PICOPASS_BLOCK_SIZE), PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
free(dump);
|
|
PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2zd") " keys from %s", i, filename);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int iclass_print_keys(void) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "idx| key");
|
|
PrintAndLogEx(INFO, "---+------------------------");
|
|
for (uint8_t i = 0; i < ICLASS_KEYS_MAX; i++) {
|
|
if (memcmp(iClass_Key_Table[i], zeros, sizeof(zeros)) == 0)
|
|
PrintAndLogEx(INFO, " %u |", i);
|
|
else
|
|
PrintAndLogEx(INFO, " %u | " _YELLOW_("%s"), i, sprint_hex(iClass_Key_Table[i], PICOPASS_BLOCK_SIZE));
|
|
}
|
|
PrintAndLogEx(INFO, "---+------------------------");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassManageKeys(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass managekeys",
|
|
"Manage iCLASS Keys in client memory",
|
|
"hf iclass managekeys --ki 0 -k 1122334455667788 --> set key 1122334455667788 at index 0\n"
|
|
"hf iclass managekeys -f mykeys.bin --save --> save key file\n"
|
|
"hf iclass managekeys -f mykeys.bin --load --> load key file\n"
|
|
"hf iclass managekeys -p --> print keys");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Specify a filename for load / save operations"),
|
|
arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Specify key index to set key in memory"),
|
|
arg_lit0(NULL, "save", "Save keys in memory to file specified by filename"),
|
|
arg_lit0(NULL, "load", "Load keys to memory from file specified by filename"),
|
|
arg_lit0("p", "print", "Print keys loaded into memory"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
int key_len = 0;
|
|
uint8_t key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, key, &key_len);
|
|
uint8_t operation = 0;
|
|
|
|
if (key_len > 0) {
|
|
operation += 3;
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Key is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
int key_nr = arg_get_int_def(ctx, 3, -1);
|
|
|
|
if (key_nr >= 0) {
|
|
if (key_nr < ICLASS_KEYS_MAX) {
|
|
PrintAndLogEx(SUCCESS, "Current key[%d] " _YELLOW_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key index is out-of-range");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (arg_get_lit(ctx, 4)) { //save
|
|
operation += 6;
|
|
}
|
|
if (arg_get_lit(ctx, 5)) { //load
|
|
operation += 5;
|
|
}
|
|
if (arg_get_lit(ctx, 6)) { //print
|
|
operation += 4;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (operation == 0) {
|
|
PrintAndLogEx(ERR, "No operation specified (load, save, or print)\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (operation > 6) {
|
|
PrintAndLogEx(ERR, "Too many operations specified\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (operation > 4 && fnlen == 0) {
|
|
PrintAndLogEx(ERR, "You must enter a filename when loading or saving\n");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (key_len > 0 && key_nr == -1) {
|
|
PrintAndLogEx(ERR, "Please specify key index when specifying key");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
switch (operation) {
|
|
case 3:
|
|
memcpy(iClass_Key_Table[key_nr], key, 8);
|
|
PrintAndLogEx(SUCCESS, " New key[%d] " _GREEN_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
|
|
return PM3_SUCCESS;
|
|
case 4:
|
|
return iclass_print_keys();
|
|
case 5:
|
|
return iclass_load_keys(filename);
|
|
case 6: {
|
|
bool isOK = saveFile(filename, ".bin", iClass_Key_Table, sizeof(iClass_Key_Table));
|
|
if (isOK == false) {
|
|
return PM3_EFILE;
|
|
}
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static void add_key(uint8_t *key) {
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < ICLASS_KEYS_MAX; i++) {
|
|
|
|
if (memcmp(iClass_Key_Table[i], key, 8) == 0) {
|
|
PrintAndLogEx(SUCCESS, "Key already at keyslot " _GREEN_("%d"), i);
|
|
break;
|
|
}
|
|
|
|
if (memcmp(iClass_Key_Table[i], "\x00\x00\x00\x00\x00\x00\x00\x00", 8) == 0) {
|
|
memcpy(iClass_Key_Table[i], key, 8);
|
|
PrintAndLogEx(SUCCESS, "Added key to keyslot " _GREEN_("%d"), i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ICLASS_KEYS_MAX) {
|
|
PrintAndLogEx(INFO, "Couldn't find an empty keyslot");
|
|
} else {
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass managekeys -p") "` to view keys");
|
|
}
|
|
}
|
|
|
|
static int CmdHFiClassCheckKeys(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass chk",
|
|
"Checkkeys loads a dictionary text file with 8 byte hex keys to test authenticating against a iCLASS tag",
|
|
"hf iclass chk -f iclass_default_keys.dic\n"
|
|
"hf iclass chk -f iclass_elite_keys.dic --elite\n"
|
|
"hf iclass chk --vb6kdf\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Dictionary file with default iclass keys"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key (raw)"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0(NULL, "vb6kdf", "use the VB6 elite KDF instead of a file"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
bool use_vb6kdf = arg_get_lit(ctx, 6);
|
|
bool use_elite = arg_get_lit(ctx, 3);
|
|
bool use_raw = arg_get_lit(ctx, 4);
|
|
if (use_vb6kdf) {
|
|
use_elite = true;
|
|
} else {
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
}
|
|
|
|
bool use_credit_key = arg_get_lit(ctx, 2);
|
|
bool shallow_mod = arg_get_lit(ctx, 5);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t CSN[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
// no filename and don't use algorithm for elite
|
|
// just add the default dictionary
|
|
if ((strlen(filename) == 0) && (use_vb6kdf == false)) {
|
|
|
|
if (use_elite) {
|
|
PrintAndLogEx(INFO, "Using default elite dictionary");
|
|
snprintf(filename, sizeof(filename), ICLASS_DEFAULT_KEY_ELITE_DIC);
|
|
} else {
|
|
PrintAndLogEx(INFO, "Using default dictionary");
|
|
snprintf(filename, sizeof(filename), ICLASS_DEFAULT_KEY_DIC);
|
|
}
|
|
}
|
|
|
|
uint64_t t1 = msclock();
|
|
|
|
// load keys
|
|
uint8_t *keyBlock = NULL;
|
|
uint32_t keycount = 0;
|
|
|
|
if (use_vb6kdf) {
|
|
// Generate 5000 keys using VB6 KDF
|
|
keycount = 5000;
|
|
keyBlock = calloc(1, keycount * 8);
|
|
if (keyBlock == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
picopass_elite_reset();
|
|
for (uint32_t i = 0; i < keycount; i++) {
|
|
picopass_elite_nextKey(keyBlock + (i * 8));
|
|
}
|
|
} else {
|
|
// Load keys
|
|
int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
|
|
if (res != PM3_SUCCESS || keycount == 0) {
|
|
free(keyBlock);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// limit size of keys that can be held in memory
|
|
if (keycount > 100000) {
|
|
PrintAndLogEx(FAILED, "File contains more than 100 000 keys, aborting...");
|
|
free(keyBlock);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
// Get CSN / UID and CCNR
|
|
PrintAndLogEx(SUCCESS, "Reading tag CSN / CCNR...");
|
|
|
|
bool got_csn = false;
|
|
for (uint8_t i = 0; i < ICLASS_AUTH_RETRY; i++) {
|
|
got_csn = select_only(CSN, CCNR, false, shallow_mod);
|
|
if (got_csn == false)
|
|
PrintAndLogEx(WARNING, "one more try");
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (got_csn == false) {
|
|
PrintAndLogEx(WARNING, "Tried %d times. Can't select card, aborting...", ICLASS_AUTH_RETRY);
|
|
free(keyBlock);
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// allocate memory for the pre calculated macs
|
|
iclass_premac_t *pre = calloc(keycount, sizeof(iclass_premac_t));
|
|
if (pre == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s"), sprint_hex(CSN, sizeof(CSN)));
|
|
PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
|
|
|
|
PrintAndLogEx(INFO, "Generating diversified keys %s", (use_elite || use_raw) ? NOLF : "");
|
|
|
|
if (use_elite)
|
|
PrintAndLogEx(NORMAL, "using " _YELLOW_("elite algo"));
|
|
|
|
if (use_raw)
|
|
PrintAndLogEx(NORMAL, "using " _YELLOW_("raw mode"));
|
|
|
|
GenerateMacFrom(CSN, CCNR, use_raw, use_elite, keyBlock, keycount, pre);
|
|
|
|
PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", (use_credit_key) ? "CREDIT" : "DEBIT");
|
|
|
|
// USB_COMMAND. 512/4 = 103 mac
|
|
uint32_t max_chunk_size = 0;
|
|
if (keycount > ((PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4))
|
|
max_chunk_size = (PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4;
|
|
else
|
|
max_chunk_size = keycount;
|
|
|
|
// fast push mode
|
|
g_conn.block_after_ACK = true;
|
|
|
|
// keep track of position of found key
|
|
uint32_t chunk_offset = 0;
|
|
uint8_t found_offset = 0;
|
|
bool found_key = false;
|
|
|
|
// We have
|
|
// - a list of keys.
|
|
// - a list of precalculated macs that corresponds to the key list
|
|
// We send a chunk of macs to the device each time
|
|
|
|
// main keychunk loop
|
|
for (chunk_offset = 0; chunk_offset < keycount; chunk_offset += max_chunk_size) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "aborted via keyboard!");
|
|
goto out;
|
|
}
|
|
|
|
uint32_t curr_chunk_cnt = keycount - chunk_offset;
|
|
if ((keycount - chunk_offset) > max_chunk_size) {
|
|
curr_chunk_cnt = max_chunk_size;
|
|
}
|
|
|
|
// last chunk?
|
|
if (curr_chunk_cnt == keycount - chunk_offset) {
|
|
// Disable fast mode on last command
|
|
g_conn.block_after_ACK = false;
|
|
}
|
|
|
|
uint32_t tmp_plen = sizeof(iclass_chk_t) + (4 * curr_chunk_cnt);
|
|
iclass_chk_t *packet = calloc(tmp_plen, sizeof(uint8_t));
|
|
if (packet == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
break;
|
|
}
|
|
packet->use_credit_key = use_credit_key;
|
|
packet->count = curr_chunk_cnt;
|
|
packet->shallow_mod = shallow_mod;
|
|
// copy chunk of pre calculated macs to packet
|
|
memcpy(packet->items, (pre + chunk_offset), (4 * curr_chunk_cnt));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_CHKKEYS, (uint8_t *)packet, tmp_plen);
|
|
free(packet);
|
|
|
|
bool looped = false;
|
|
uint8_t timeout = 0;
|
|
|
|
PacketResponseNG resp;
|
|
while (WaitForResponseTimeout(CMD_HF_ICLASS_CHKKEYS, &resp, 2000) == false) {
|
|
timeout++;
|
|
PrintAndLogEx(NORMAL, "." NOLF);
|
|
if (timeout > 10) {
|
|
PrintAndLogEx(WARNING, "\ncommand execution time out, aborting...");
|
|
goto out;
|
|
}
|
|
looped = true;
|
|
}
|
|
|
|
if (looped)
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
found_offset = resp.data.asBytes[0];
|
|
found_key = true;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS,
|
|
"Found valid key " _GREEN_("%s")
|
|
, sprint_hex(keyBlock + (chunk_offset + found_offset) * 8, 8)
|
|
);
|
|
break;
|
|
} else {
|
|
PrintAndLogEx(INPLACE, "Chunk [%03d/%d]", chunk_offset, keycount);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
out:
|
|
t1 = msclock() - t1;
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "time in iclass chk " _YELLOW_("%.1f") " seconds", (float)t1 / 1000.0);
|
|
DropField();
|
|
|
|
if (found_key) {
|
|
uint8_t *key = keyBlock + (chunk_offset + found_offset) * 8;
|
|
add_key(key);
|
|
}
|
|
|
|
free(pre);
|
|
free(keyBlock);
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
|
|
// this method tries to identify in which configuration mode a iCLASS / iCLASS SE reader is in.
|
|
// Standard or Elite / HighSecurity mode. It uses a default key dictionary list in order to work.
|
|
#define INITIAL_SEED 0x429080 // VB6 KDF Seed Value
|
|
|
|
// Functions for generating keys using RNG
|
|
uint32_t seed = INITIAL_SEED;
|
|
uint8_t key_state[8];
|
|
bool prepared = false;
|
|
|
|
void picopass_elite_reset(void) {
|
|
memset(key_state, 0, sizeof(key_state));
|
|
seed = INITIAL_SEED;
|
|
prepared = false;
|
|
}
|
|
|
|
uint32_t picopass_elite_lcg(void) {
|
|
uint32_t mod = 0x1000000; // 2^24
|
|
uint32_t a = 0xFD43FD;
|
|
uint32_t c = 0xC39EC3;
|
|
|
|
return (a * seed + c) % mod;
|
|
}
|
|
|
|
uint32_t picopass_elite_rng(void) {
|
|
seed = picopass_elite_lcg();
|
|
return seed;
|
|
}
|
|
|
|
uint8_t picopass_elite_nextByte(void) {
|
|
return (picopass_elite_rng() >> 16) & 0xFF;
|
|
}
|
|
|
|
void picopass_elite_nextKey(uint8_t *key) {
|
|
if (prepared) {
|
|
for (size_t i = 0; i < 7; i++) {
|
|
key_state[i] = key_state[i + 1];
|
|
}
|
|
key_state[7] = picopass_elite_nextByte();
|
|
} else {
|
|
for (size_t i = 0; i < 8; i++) {
|
|
key_state[i] = picopass_elite_nextByte();
|
|
}
|
|
prepared = true;
|
|
}
|
|
memcpy(key, key_state, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
static int iclass_recover(uint8_t key[8], uint32_t index_start, uint32_t loop, uint8_t no_first_auth[8], bool debug, bool test, bool fast, bool short_delay, bool allnight) {
|
|
|
|
int runs = 1;
|
|
int cycle = 1;
|
|
bool repeat = true;
|
|
if (allnight) {
|
|
runs = 10;
|
|
}
|
|
|
|
while (repeat == true) {
|
|
|
|
uint32_t payload_size = sizeof(iclass_recover_req_t);
|
|
|
|
iclass_recover_req_t *payload = calloc(1, payload_size);
|
|
if (payload == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
break;
|
|
}
|
|
|
|
payload->req.use_raw = true;
|
|
payload->req.use_elite = false;
|
|
payload->req.use_credit_key = false;
|
|
payload->req.use_replay = true;
|
|
payload->req.send_reply = true;
|
|
payload->req.do_auth = true;
|
|
payload->req.shallow_mod = false;
|
|
payload->index = index_start;
|
|
payload->loop = loop;
|
|
payload->debug = debug;
|
|
payload->test = test;
|
|
payload->fast = fast;
|
|
payload->short_delay = short_delay;
|
|
memcpy(payload->nfa, no_first_auth, PICOPASS_BLOCK_SIZE);
|
|
memcpy(payload->req.key, key, PICOPASS_BLOCK_SIZE);
|
|
|
|
PrintAndLogEx(INFO, "Recover started...");
|
|
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_RECOVER, (uint8_t *)payload, payload_size);
|
|
WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp);
|
|
|
|
if (resp.status == PM3_SUCCESS) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!"));
|
|
repeat = false;
|
|
} else if (resp.status == PM3_EOPABORTED) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _YELLOW_("aborted via keyboard!"));
|
|
repeat = false;
|
|
} else if (resp.status == PM3_ESOFT) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _RED_("failed/errors"));
|
|
repeat = false;
|
|
} else if (resp.status == PM3_EINVARG) {
|
|
if (allnight) {
|
|
if (runs <= cycle) {
|
|
repeat = false;
|
|
} else {
|
|
index_start = index_start + loop;
|
|
cycle++;
|
|
}
|
|
} else {
|
|
repeat = false;
|
|
}
|
|
}
|
|
|
|
free(payload);
|
|
|
|
if (repeat == false) {
|
|
return resp.status;
|
|
}
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uint8_t *keyBlock) {
|
|
uint64_t carry = index;
|
|
memcpy(keyBlock, startingKey, PICOPASS_BLOCK_SIZE);
|
|
|
|
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
|
|
uint8_t increment_value = (carry & 0x1F) << 3; // Use the first 5 bits of carry and shift left by 3 to occupy the first 5 bits
|
|
keyBlock[j] = (keyBlock[j] & 0x07) | increment_value; // Preserve last 3 bits, modify the first 5 bits
|
|
|
|
carry >>= 5; // Shift right by 5 bits for the next byte
|
|
if (carry == 0) {
|
|
// If no more carry, break early to avoid unnecessary loops
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// HF iClass legbrute - Thread argument structure
|
|
typedef struct {
|
|
uint8_t startingKey[8];
|
|
uint64_t index_start;
|
|
uint64_t index_end;
|
|
uint8_t CCNR1[12];
|
|
uint8_t MAC_TAG1[4];
|
|
uint8_t CCNR2[12];
|
|
uint8_t MAC_TAG2[4];
|
|
int thread_id;
|
|
int thread_count;
|
|
uint64_t start_time;
|
|
_Atomic bool *found;
|
|
_Atomic bool *aborted;
|
|
_Atomic uint64_t *aborted_at;
|
|
bool debug;
|
|
pthread_mutex_t *log_lock;
|
|
} thread_args_t;
|
|
|
|
// Lock-guarded "found" announcement; factored out because the bitslice and
|
|
// scalar paths both reach it.
|
|
static void legbrute_announce(thread_args_t *args, const uint8_t div_key[8]) {
|
|
pthread_mutex_lock(args->log_lock);
|
|
if (!*(args->found)) {
|
|
*args->found = true;
|
|
PrintAndLogEx(NORMAL, "\n");
|
|
PrintAndLogEx(SUCCESS, "Found valid raw key " _GREEN_("%s"), sprint_hex_inrow(div_key, 8));
|
|
PrintAndLogEx(HINT, "Hint: Run `"_YELLOW_("hf iclass unhash -k %s")"` to find the needed pre-images", sprint_hex_inrow(div_key, 8));
|
|
PrintAndLogEx(INFO, "Done!");
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
pthread_mutex_unlock(args->log_lock);
|
|
}
|
|
|
|
// HF iClass legbrute - Brute-force worker thread
|
|
static void *brute_thread(void *args_void) {
|
|
|
|
thread_args_t *args = (thread_args_t *)args_void;
|
|
uint8_t div_key[8];
|
|
uint64_t index = args->index_start;
|
|
|
|
// Scalar per-candidate hot-loop expansions (used in the alignment
|
|
// prefix/tail and for MAC2 verification after a bitslice hit).
|
|
uint8_t y_bits1[96];
|
|
uint8_t y_bits2[96];
|
|
prepare_ccnr_bits(args->CCNR1, y_bits1);
|
|
prepare_ccnr_bits(args->CCNR2, y_bits2);
|
|
|
|
// Bitslice expansions for the fast path. The backend is picked once at
|
|
// startup (widest SIMD width the CPU supports: AVX-512 > AVX2 > NEON > u64).
|
|
// MAC2 stays scalar — it only runs on the vanishingly rare MAC1 collisions,
|
|
// so there is no reason to pre-expand it.
|
|
const bs_backend_t *bs = bs_best_backend();
|
|
const uint64_t bs_align = (uint64_t)(bs->width - 1);
|
|
uint64_t y_bits1_bs[96 * BS_MAX_WORDS];
|
|
uint64_t target_mac1_bs[32 * BS_MAX_WORDS];
|
|
bs->prepare_ccnr(args->CCNR1, y_bits1_bs);
|
|
bs->prepare_mac(args->MAC_TAG1, target_mac1_bs);
|
|
|
|
if (args->debug) {
|
|
pthread_mutex_lock(args->log_lock);
|
|
|
|
PrintAndLogEx(INFO, "Thread[%2d] range [%" PRIu64 " - %" PRIu64 ") startingKey: %s"
|
|
, args->thread_id
|
|
, args->index_start
|
|
, args->index_end
|
|
, sprint_hex_inrow(args->startingKey, 8));
|
|
|
|
// Show first 2 candidates — different threads must start from different candidates
|
|
for (int d = 0; d < 2; d++) {
|
|
generate_key_block_inverted(args->startingKey, args->index_start + d, div_key);
|
|
PrintAndLogEx(INFO, " [index %" PRIu64 "]: %s", args->index_start + d, sprint_hex_inrow(div_key, 8));
|
|
}
|
|
|
|
// Show the midpoint of the slice — confirms byte-0 carry is reached inside this thread's range
|
|
uint64_t mid = args->index_start + (args->index_end - args->index_start) / 2;
|
|
generate_key_block_inverted(args->startingKey, mid, div_key);
|
|
PrintAndLogEx(INFO, " [index %" PRIu64 " (mid)]: %s", mid, sprint_hex_inrow(div_key, 8));
|
|
|
|
pthread_mutex_unlock(args->log_lock);
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t progress_countdown = 1000000;
|
|
while (index < args->index_end && !*(args->found) && !*(args->aborted)) {
|
|
|
|
uint64_t step;
|
|
const uint64_t remaining = args->index_end - index;
|
|
|
|
if ((index & bs_align) == 0 && remaining >= (uint64_t)bs->width) {
|
|
// Fast path: bs->width-wide bitslice MAC1 sweep. Build the key
|
|
// schedule for W consecutive candidates and test them in parallel.
|
|
uint64_t kb[64 * BS_MAX_WORDS];
|
|
bs->build_key(args->startingKey, index, kb);
|
|
|
|
uint64_t match[BS_MAX_WORDS];
|
|
bs->match(y_bits1_bs, kb, target_mac1_bs, match);
|
|
|
|
// MAC1 collisions are ~W / 2^32 per batch on average (i.e., zero
|
|
// until the true key's batch is reached). For each surviving lane,
|
|
// reconstruct its div_key and verify against MAC2 with the scalar
|
|
// prebit matcher.
|
|
bool done = false;
|
|
for (int w = 0; w < bs->words && !done; w++) {
|
|
uint64_t m = match[w];
|
|
while (m != 0) {
|
|
const int L = __builtin_ctzll(m);
|
|
m &= m - 1;
|
|
const uint64_t cand = index + (uint64_t)(w * 64 + L);
|
|
generate_key_block_inverted(args->startingKey, cand, div_key);
|
|
if (doMAC_brute_match_prebit(y_bits2, div_key, args->MAC_TAG2)) {
|
|
legbrute_announce(args, div_key);
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
step = (uint64_t)bs->width;
|
|
} else {
|
|
// Scalar fallback: non-64-aligned prefix, the sub-64-candidate
|
|
// tail, or any thread whose slice is not a multiple of 64.
|
|
generate_key_block_inverted(args->startingKey, index, div_key);
|
|
|
|
if (doMAC_brute_match_prebit(y_bits1, div_key, args->MAC_TAG1)) {
|
|
if (doMAC_brute_match_prebit(y_bits2, div_key, args->MAC_TAG2)) {
|
|
legbrute_announce(args, div_key);
|
|
}
|
|
}
|
|
|
|
step = 1;
|
|
}
|
|
|
|
uint64_t thread_progress = index - args->index_start;
|
|
if (progress_countdown <= step && !*(args->found)) {
|
|
progress_countdown = 100000000;
|
|
|
|
if (args->thread_id == 0) {
|
|
uint64_t keyspace = (uint64_t)1 << 40;
|
|
uint64_t keyspace_m = keyspace / 1000000; // 1,099,511
|
|
uint64_t run_progress = thread_progress * (uint64_t)args->thread_count;
|
|
// Cumulative absolute position across all threads including any --index offset
|
|
uint64_t abs_done = args->index_start * (uint64_t)args->thread_count + run_progress;
|
|
uint64_t keys_left = (abs_done < keyspace) ? keyspace - abs_done : 0;
|
|
uint64_t elapsed_ms = msclock() - args->start_time;
|
|
|
|
pthread_mutex_lock(args->log_lock);
|
|
|
|
if (elapsed_ms > 0 && run_progress > 0) {
|
|
// speed based on keys tested in this run only
|
|
uint64_t kps = run_progress * 1000 / elapsed_ms;
|
|
uint64_t eta_s = (kps > 0) ? keys_left / kps : 0;
|
|
uint64_t eta_d = eta_s / 86400;
|
|
uint64_t eta_h = (eta_s % 86400) / 3600;
|
|
uint64_t eta_m = (eta_s % 3600) / 60;
|
|
uint64_t eta_r = eta_s % 60;
|
|
PrintAndLogEx(INPLACE, "Tested "_YELLOW_("%" PRIu64)"M / %" PRIu64 "M keys speed: "_YELLOW_("%" PRIu64)" k/s ETA: "_YELLOW_("%" PRIu64 "d %02" PRIu64 "h %02" PRIu64 "m %02" PRIu64 "s")
|
|
, abs_done / 1000000
|
|
, keyspace_m
|
|
, kps / 1000
|
|
, eta_d, eta_h, eta_m, eta_r
|
|
);
|
|
} else {
|
|
PrintAndLogEx(INPLACE, "Tested "_YELLOW_("%" PRIu64)"M / %" PRIu64 "M keys"
|
|
, abs_done / 1000000
|
|
, keyspace_m
|
|
);
|
|
}
|
|
|
|
if (kbd_enter_pressed()) {
|
|
*args->aborted_at = index;
|
|
*args->aborted = true;
|
|
}
|
|
pthread_mutex_unlock(args->log_lock);
|
|
}
|
|
|
|
} else {
|
|
progress_countdown -= (uint32_t)step;
|
|
}
|
|
index += step;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// HF iClass legbrute - Multithreaded brute-force function
|
|
static int CmdHFiClassLegBrute_MT(uint8_t epurse[8], uint8_t macs[8], uint8_t macs2[8], uint8_t startingKey[8], uint64_t index, int threads, bool debug) {
|
|
|
|
int thread_count = threads;
|
|
if (thread_count < 1) {
|
|
thread_count = 1;
|
|
}
|
|
int max_threads = num_CPUs();
|
|
if (thread_count > max_threads) {
|
|
PrintAndLogEx(INFO, "Capping threads at available CPU count (%d)", max_threads);
|
|
thread_count = max_threads;
|
|
}
|
|
const bs_backend_t *bs = bs_best_backend();
|
|
PrintAndLogEx(INFO, "Bruteforcing using " _YELLOW_("%u") " threads, " _YELLOW_("%s") " bitslice (%d lanes)",
|
|
thread_count, bs->name, bs->width);
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
uint8_t CCNR[12], CCNR2[12], MAC_TAG[4], MAC_TAG2[4];
|
|
|
|
memcpy(CCNR, epurse, 8);
|
|
memcpy(CCNR2, epurse, 8);
|
|
memcpy(CCNR + 8, macs, 4);
|
|
memcpy(CCNR2 + 8, macs2, 4);
|
|
memcpy(MAC_TAG, macs + 4, 4);
|
|
memcpy(MAC_TAG2, macs2 + 4, 4);
|
|
|
|
pthread_t tids[thread_count];
|
|
thread_args_t args[thread_count];
|
|
_Atomic bool found = false;
|
|
_Atomic bool aborted = false;
|
|
_Atomic uint64_t aborted_at = 0;
|
|
pthread_mutex_t log_lock;
|
|
pthread_mutex_init(&log_lock, NULL);
|
|
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
|
|
// Divide the full 40-bit keyspace into equal non-overlapping slices, one per thread.
|
|
// All threads use the same startingKey; only their index range differs.
|
|
uint64_t keyspace = (uint64_t)1 << 40;
|
|
uint64_t slice = keyspace / thread_count;
|
|
uint64_t start_time = msclock();
|
|
|
|
for (int i = 0; i < thread_count; i++) {
|
|
memcpy(args[i].startingKey, startingKey, 8);
|
|
args[i].index_start = index + (uint64_t)i * slice;
|
|
args[i].index_end = (i == thread_count - 1)
|
|
? index + keyspace // last thread absorbs remainder
|
|
: index + (uint64_t)(i + 1) * slice;
|
|
memcpy(args[i].CCNR1, CCNR, 12);
|
|
memcpy(args[i].MAC_TAG1, MAC_TAG, 4);
|
|
memcpy(args[i].CCNR2, CCNR2, 12);
|
|
memcpy(args[i].MAC_TAG2, MAC_TAG2, 4);
|
|
args[i].thread_id = i;
|
|
args[i].thread_count = thread_count;
|
|
args[i].start_time = start_time;
|
|
args[i].found = &found;
|
|
args[i].aborted = &aborted;
|
|
args[i].aborted_at = &aborted_at;
|
|
args[i].debug = debug;
|
|
args[i].log_lock = &log_lock;
|
|
|
|
if (pthread_create(&tids[i], NULL, brute_thread, &args[i]) != 0) {
|
|
PrintAndLogEx(WARNING, "Failed to create thread %d, running with %d thread(s)", i, i);
|
|
thread_count = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (thread_count == 0) {
|
|
pthread_mutex_destroy(&log_lock);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
for (int i = 0; i < thread_count; i++) {
|
|
pthread_join(tids[i], NULL);
|
|
}
|
|
pthread_mutex_destroy(&log_lock);
|
|
|
|
if (debug) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Index range summary (%d threads, keyspace 2^40 = %" PRIu64 "):", thread_count, (uint64_t)1 << 40);
|
|
PrintAndLogEx(INFO, " Thread start end slice size");
|
|
for (int i = 0; i < thread_count; i++) {
|
|
PrintAndLogEx(INFO, " [%2d] %-20" PRIu64 " %-20" PRIu64 " %" PRIu64
|
|
, i
|
|
, args[i].index_start
|
|
, args[i].index_end
|
|
, args[i].index_end - args[i].index_start);
|
|
}
|
|
// Verify ranges are contiguous and non-overlapping
|
|
bool ok = true;
|
|
for (int i = 1; i < thread_count; i++) {
|
|
if (args[i].index_start != args[i - 1].index_end) {
|
|
PrintAndLogEx(WARNING, _RED_(" Gap or overlap between thread %d and %d!"), i - 1, i);
|
|
ok = false;
|
|
}
|
|
}
|
|
if (ok) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_(" Ranges are contiguous and non-overlapping"));
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (aborted) {
|
|
uint64_t resume_millions = aborted_at / 1000000;
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard!");
|
|
PrintAndLogEx(HINT, "Hint: resume with " _YELLOW_("--index %" PRIu64 " --threads %d"), resume_millions, thread_count);
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
if (found == false) {
|
|
PrintAndLogEx(WARNING, "Key not found in the given keyspace");
|
|
}
|
|
|
|
return found ? PM3_SUCCESS : PM3_ESOFT;
|
|
}
|
|
|
|
// CmdHFiClassLegBrute function with CLI and multithreading support
|
|
static int CmdHFiClassLegBrute(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass legbrute",
|
|
"This command takes sniffed trace data and a partial raw key and bruteforces the remaining 40 bits of the raw key.\n"
|
|
"Complete 40 bit keyspace is 1'099'511'627'776.",
|
|
"hf iclass legbrute --epurse feffffffffffffff --macs1 1306cad9b6c24466 --macs2 f0bf905e35f97923 --pk 0401020505000205");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
|
|
arg_str1(NULL, "macs1", "<hex>", "MACs captured from the reader"),
|
|
arg_str1(NULL, "macs2", "<hex>", "MACs captured from the reader, different than the first set (with the same csn and epurse value)"),
|
|
arg_str1(NULL, "pk", "<hex>", "Partial Key from legrec or starting key of keyblock from legbrute"),
|
|
arg_int0(NULL, "index", "<dec>", "Where to start from to retrieve the key, default 0 - value in millions e.g. 1 is 1 million"),
|
|
arg_int0(NULL, "threads", "<dec>", "Number of threads to use, by default it uses the cpu's max threads."),
|
|
arg_lit0(NULL, "dbg", "Print first 2 key candidates and midpoint per thread, then exit (use to verify thread partitioning)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int epurse_len = 0;
|
|
uint8_t epurse[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 1, epurse, &epurse_len);
|
|
|
|
int macs_len = 0;
|
|
uint8_t macs[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, macs, &macs_len);
|
|
|
|
int macs2_len = 0;
|
|
uint8_t macs2[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, macs2, &macs2_len);
|
|
|
|
int startingkey_len = 0;
|
|
uint8_t startingKey[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, startingKey, &startingkey_len);
|
|
|
|
uint64_t index = arg_get_int_def(ctx, 5, 0);
|
|
index *= 1000000;
|
|
int threads = arg_get_int_def(ctx, 6, num_CPUs());
|
|
bool debug = arg_get_lit(ctx, 7);
|
|
CLIParserFree(ctx);
|
|
|
|
if (epurse_len && epurse_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "ePurse is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (macs_len && macs_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "MAC1 is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (macs2_len && macs2_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "MAC2 is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (startingkey_len && startingkey_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "Partial Key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
return CmdHFiClassLegBrute_MT(epurse, macs, macs2, startingKey, index, threads, debug);
|
|
}
|
|
|
|
static void generate_single_key_block_inverted_opt(const uint8_t *startingKey, uint32_t index, uint8_t *keyBlock) {
|
|
|
|
uint8_t bits_index = index / 16383;
|
|
uint8_t ending_bits[] = { //all possible 70 combinations of 4x0 and 4x1 as key ending bits
|
|
0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E, 0x33,
|
|
0x35, 0x36, 0x39, 0x3A, 0x3C, 0x47, 0x4B, 0x4D, 0x4E, 0x53,
|
|
0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65, 0x66, 0x69, 0x6A,
|
|
0x6C, 0x71, 0x72, 0x74, 0x78, 0x87, 0x8B, 0x8D, 0x8E, 0x93,
|
|
0x95, 0x96, 0x99, 0x9A, 0x9C, 0xA3, 0xA5, 0xA6, 0xA9, 0xAA,
|
|
0xAC, 0xB1, 0xB2, 0xB4, 0xB8, 0xC3, 0xC5, 0xC6, 0xC9, 0xCA,
|
|
0xCC, 0xD1, 0xD2, 0xD4, 0xD8, 0xE1, 0xE2, 0xE4, 0xE8, 0xF0
|
|
};
|
|
|
|
uint8_t binary_endings[8]; // Array to store binary values for each ending bit
|
|
// Extract each bit from the ending_bits[k] and store it in binary_endings
|
|
uint8_t ending = ending_bits[bits_index];
|
|
for (int i = 7; i >= 0; i--) {
|
|
binary_endings[i] = ending & 1;
|
|
ending >>= 1;
|
|
}
|
|
|
|
uint8_t binary_mids[8]; // Array to store the 2-bit chunks of index
|
|
// Iterate over the 16-bit integer and store 2 bits at a time in the result array
|
|
for (int i = 0; i < 8; i++) {
|
|
// Shift and mask to get 2 bits and store them as an 8-bit value
|
|
binary_mids[7 - i] = (index >> (i * 2)) & 0x03; // 0x03 is a mask for 2 bits (binary 11)
|
|
}
|
|
|
|
memcpy(keyBlock, startingKey, PICOPASS_BLOCK_SIZE);
|
|
|
|
// Start from the second byte, index 1 as we're never gonna touch the first byte
|
|
for (int i = 1; i < PICOPASS_BLOCK_SIZE; i++) {
|
|
// Clear the last bit of the current byte (AND with 0xFE)
|
|
keyBlock[i] &= 0xF8;
|
|
// Set the last bit to the corresponding value from binary_endings (OR with binary_endings[i])
|
|
keyBlock[i] |= ((binary_mids[i] & 0x03) << 1) | (binary_endings[i] & 0x01);
|
|
}
|
|
|
|
}
|
|
|
|
static int CmdHFiClassLegacyRecSim(void) {
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, _YELLOW_("This simulation assumes the card is standard keyed."));
|
|
PrintAndLogEx(INFO, "");
|
|
|
|
uint8_t csn[8] = {0};
|
|
uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
if (select_only(csn, CCNR, true, false) == false) {
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t new_div_key[8] = {0};
|
|
HFiClassCalcDivKey(csn, iClass_Key_Table[0], new_div_key, false);
|
|
|
|
uint8_t key[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t original_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
|
|
memcpy(key, new_div_key, PICOPASS_BLOCK_SIZE);
|
|
memcpy(original_key, key, PICOPASS_BLOCK_SIZE);
|
|
|
|
uint8_t zero_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
int bits_found = -1;
|
|
uint32_t index = 0;
|
|
|
|
#define MAX_UPDATES 16777216
|
|
|
|
while (bits_found == -1 && index < MAX_UPDATES) {
|
|
|
|
uint8_t genkeyblock[PICOPASS_BLOCK_SIZE] = {0};
|
|
|
|
generate_single_key_block_inverted_opt(zero_key, index, genkeyblock);
|
|
|
|
for (int i = 0; i < 8 ; i++) {
|
|
key[i] = genkeyblock[i] ^ original_key[i];
|
|
}
|
|
|
|
// Extract the last 3 bits of the first byte
|
|
uint8_t last_three_bits = key[0] & 0x07; // 0x07 is 00000111 in binary - bitmask
|
|
|
|
bool same_bits = true;
|
|
// Check if the last 3 bits of all bytes are the same
|
|
for (int i = 1; i < PICOPASS_BLOCK_SIZE; i++) {
|
|
if ((key[i] & 0x07) != last_three_bits) {
|
|
same_bits = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (same_bits) {
|
|
PrintAndLogEx(SUCCESS, "Original key... " _GREEN_("%s"), sprint_hex_inrow(original_key, sizeof(original_key)));
|
|
PrintAndLogEx(SUCCESS, "Weak key....... " _YELLOW_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
PrintAndLogEx(SUCCESS, "Key updates required to weak key..... " _GREEN_("%d"), index);
|
|
PrintAndLogEx(SUCCESS, "Estimated time ( default mode )...... " _GREEN_("~%d")" hours", index / 17800);
|
|
PrintAndLogEx(SUCCESS, "Estimated time ( default + --sl ).... " _GREEN_("~%d")" hours", index / 19450);
|
|
PrintAndLogEx(SUCCESS, "Estimated time ( --fast mode )....... " _GREEN_("~%d")" hours", index / 26860);
|
|
PrintAndLogEx(SUCCESS, "Estimated time ( --fast + --sl )..... " _GREEN_("~%d")" hours", index / 29750);
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
} // end while
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
|
|
}
|
|
|
|
static int CmdHFiClassLegacyRecover(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass legrec",
|
|
"Attempts to recover the diversified key of a specific iCLASS card. This may take several days.\n"
|
|
"The card must remain on the PM3 antenna during the whole process.\n"
|
|
_RED_(" ! Warning ! ") _WHITE_(" This process may brick the card! ") _RED_(" ! Warning ! "),
|
|
"hf iclass legrec --macs 0000000089cb984b\n"
|
|
"hf iclass legrec --macs 0000000089cb984b --index 0 --loop 100 --notest"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "macs", "<hex>", "AA1 Authentication MACs"),
|
|
arg_int0(NULL, "index", "<dec>", "Where to start from to retrieve the key (def: 0)"),
|
|
arg_int0(NULL, "loop", "<dec>", "The number of key retrieval cycles to perform, max 10000 (def 100)"),
|
|
arg_lit0(NULL, "debug", "Re-enables tracing for debugging. Limits cycles to 1"),
|
|
arg_lit0(NULL, "notest", "Perform real writes on the card"),
|
|
arg_lit0(NULL, "allnight", "Loops the loop for 10 times, recommended loop value of 5000"),
|
|
arg_lit0(NULL, "fast", "Increases the speed (4.6->7.4 key updates/second), higher risk to brick the card"),
|
|
arg_lit0(NULL, "sl", "Lower card comms delay times, further speeds increases, may cause more errors"),
|
|
arg_lit0(NULL, "est", "Estimates the key updates based on the card's CSN assuming standard key, can be used with --credit option"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int macs_len = 0;
|
|
uint8_t macs[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 1, macs, &macs_len);
|
|
uint32_t index = arg_get_int_def(ctx, 2, 0);
|
|
uint32_t loop = arg_get_int_def(ctx, 3, 100);
|
|
uint8_t no_first_auth[PICOPASS_BLOCK_SIZE] = {0};
|
|
bool debug = arg_get_lit(ctx, 4);
|
|
bool test = true;
|
|
bool no_test = arg_get_lit(ctx, 5);
|
|
bool allnight = arg_get_lit(ctx, 6);
|
|
bool fast = arg_get_lit(ctx, 7);
|
|
bool short_delay = arg_get_lit(ctx, 8);
|
|
bool sim = arg_get_lit(ctx, 9);
|
|
|
|
if (sim) {
|
|
CmdHFiClassLegacyRecSim();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (macs_len == 0) {
|
|
PrintAndLogEx(ERR, "Missing required argument: --macs");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (no_test) {
|
|
test = false;
|
|
}
|
|
|
|
if (loop > 10000) {
|
|
PrintAndLogEx(ERR, "Too many loops, arm prone to crashes. For safety specify a number lower than 10000");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
} else if (test) {
|
|
loop = 1;
|
|
fast = false;
|
|
} else if (debug) {
|
|
if (loop > 10) {
|
|
loop = 10;
|
|
}
|
|
fast = false;
|
|
}
|
|
|
|
uint8_t csn[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t new_div_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
if (select_only(csn, CCNR, true, false) == false) {
|
|
DropField();
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
diversifyKey(csn, iClass_Key_Table[1], new_div_key);
|
|
|
|
memcpy(no_first_auth, new_div_key, PICOPASS_BLOCK_SIZE);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (macs_len && macs_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "MAC is incorrect length");
|
|
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, fast, short_delay, allnight);
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, _YELLOW_("If the process completed successfully"));
|
|
PrintAndLogEx(HINT, "Hint-1: run `" _YELLOW_("hf iclass legbrute -h") "` with the partial key found");
|
|
PrintAndLogEx(HINT, "Hint-2: alternatively run hashcat `" _YELLOW_("./hashcat -a 3 -m 64000 hash.txt ?b?b?b?b?b") "` with the partial key found");
|
|
PrintAndLogEx(HINT, "hash.txt format would be: `" _YELLOW_("$iclass_leg$partial_key$ccnr1$mac1$ccnr2$mac2") "`");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
|
|
}
|
|
|
|
static int CmdHFiClassUnhash(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass unhash",
|
|
"Reverses the hash0 function used generate iclass diversified keys after DES encryption,\n"
|
|
"Function returns the DES crypted CSN. Next step bruteforcing.",
|
|
"hf iclass unhash -k B4F12AADC5301A2D"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1("k", "divkey", "<hex>", "Card diversified key"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int dk_len = 0;
|
|
uint8_t div_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
CLIGetHexWithReturn(ctx, 1, div_key, &dk_len);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (dk_len && dk_len != PICOPASS_BLOCK_SIZE) {
|
|
PrintAndLogEx(ERR, "Diversified key is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
//check if divkey respects hash0 rules (legacy format) or if it could be AES Based
|
|
|
|
int count_lsb0 = 0;
|
|
int count_lsb1 = 0;
|
|
|
|
for (int i = 0; i < PICOPASS_BLOCK_SIZE; i++) {
|
|
if ((div_key[i] & 0x01) == 0) {
|
|
count_lsb0++;
|
|
} else {
|
|
count_lsb1++;
|
|
}
|
|
}
|
|
|
|
if (count_lsb0 != 4 || count_lsb1 != 4) {
|
|
PrintAndLogEx(INFO, _RED_("Incorrect LSB Distribution, unable to unhash - the key might be AES based."));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Diversified key... %s", sprint_hex_inrow(div_key, sizeof(div_key)));
|
|
PrintAndLogEx(INFO, "-----------------------------------");
|
|
invert_hash0(div_key);
|
|
PrintAndLogEx(INFO, "-----------------------------------");
|
|
PrintAndLogEx(INFO, "You can now retrieve the master key by cracking DES with hashcat.");
|
|
PrintAndLogEx(INFO, "Create a text file with <preimage>:<csn> on each line and use it with hashcat.");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(HINT, "Hint: `" _YELLOW_("hashcat.exe -a 3 -m 14000 preimage:csn -1 charsets/DES_full.hcchr --hex-charset ?1?1?1?1?1?1?1?1") "`");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassLookUp(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass lookup",
|
|
"Takes sniffed trace data and tries to recover a iCLASS Standard or Elite key.\n"
|
|
"Use --live to simulate a tag, capture the reader's CHECK command on-device,\n"
|
|
"and run the lookup automatically. Built-in key table is always searched first.",
|
|
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic\n"
|
|
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite\n"
|
|
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b --vb6rng\n"
|
|
"hf iclass lookup --live\n"
|
|
"hf iclass lookup --live --csn 031fec8af7ff12e0 -f iclass_default_keys.dic"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Dictionary file with default iclass keys"),
|
|
arg_str0(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"),
|
|
arg_str0(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
|
|
arg_str0(NULL, "macs", "<hex>", "MACs (NR+MAC from sniffed trace)"),
|
|
arg_lit0(NULL, "elite", "Elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_lit0(NULL, "vb6rng", "use the VB6 rng for elite keys instead of a dictionary file"),
|
|
arg_lit0(NULL, "live", "Simulate tag, capture reader CHECK and run lookup automatically"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
|
|
int csn_len = 0;
|
|
uint8_t csn[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, csn, &csn_len);
|
|
|
|
int epurse_len = 0;
|
|
uint8_t epurse[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, epurse, &epurse_len);
|
|
|
|
int macs_len = 0;
|
|
uint8_t macs[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, macs, &macs_len);
|
|
|
|
bool use_elite = arg_get_lit(ctx, 5);
|
|
bool use_raw = arg_get_lit(ctx, 6);
|
|
bool use_vb6kdf = arg_get_lit(ctx, 7);
|
|
bool live = arg_get_lit(ctx, 8);
|
|
|
|
if (use_vb6kdf == false) {
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
// sanity checks
|
|
if (csn_len > 0 && csn_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (epurse_len > 0 && epurse_len != 8) {
|
|
PrintAndLogEx(ERR, "ePurse is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (macs_len > 0 && macs_len != 8) {
|
|
PrintAndLogEx(ERR, "MAC is incorrect length");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
uint8_t CCNR[12];
|
|
uint8_t MAC_TAG[4] = {0, 0, 0, 0};
|
|
|
|
if (live) {
|
|
// Default CSN when not provided
|
|
if (csn_len == 0) {
|
|
const uint8_t default_csn[8] = {0x03, 0x1F, 0xEC, 0x8A, 0xF7, 0xFF, 0x12, 0xE0};
|
|
memcpy(csn, default_csn, 8);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "CSN...... " _YELLOW_("%s"), sprint_hex(csn, 8));
|
|
PrintAndLogEx(INFO, "Simulating tag - waiting for reader CHECK command...");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("`pm3 button`") " to abort");
|
|
|
|
// Simulate with reader-attack mode to capture NR+MAC from the reader's CHECK command.
|
|
// Device returns: epurse[8] + NR[4] + MAC_reader[4]
|
|
PacketResponseNG resp;
|
|
clearCommandBuffer();
|
|
SendCommandMIX(CMD_HF_ICLASS_SIMULATE, ICLASS_SIM_MODE_READER_ATTACK, 1, 1, csn, 8);
|
|
|
|
uint8_t tries = 0;
|
|
while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
|
|
tries++;
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
|
return PM3_EOPABORTED;
|
|
}
|
|
if (tries > 20) {
|
|
PrintAndLogEx(WARNING, "\ntimeout while waiting for reader");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
}
|
|
|
|
uint8_t num_mac = resp.oldarg[1];
|
|
if (num_mac == 0) {
|
|
PrintAndLogEx(WARNING, "No CHECK command captured from reader");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t cap_epurse[8], nr[4], mac_r[4];
|
|
memcpy(cap_epurse, resp.data.asBytes, 8);
|
|
memcpy(nr, resp.data.asBytes + 8, 4);
|
|
memcpy(mac_r, resp.data.asBytes + 12, 4);
|
|
|
|
PrintAndLogEx(SUCCESS, "Captured CHECK:");
|
|
PrintAndLogEx(SUCCESS, " ePurse.... %s", sprint_hex(cap_epurse, 8));
|
|
PrintAndLogEx(SUCCESS, " NR........ %s", sprint_hex(nr, 4));
|
|
PrintAndLogEx(SUCCESS, " MAC_reader " _YELLOW_("%s"), sprint_hex(mac_r, 4));
|
|
|
|
memcpy(CCNR, cap_epurse, 8);
|
|
memcpy(CCNR + 8, nr, 4);
|
|
memcpy(MAC_TAG, mac_r, 4);
|
|
|
|
// Search built-in key table (standard + elite) before dictionary
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "Searching built-in key table (standard + elite)...");
|
|
bool live_found = false;
|
|
uint8_t found_key[8] = {0};
|
|
if (check_known_default(csn, cap_epurse, nr, mac_r, found_key)) {
|
|
PrintAndLogEx(SUCCESS, "Found master key " _GREEN_("%s"), sprint_hex_inrow(found_key, 8));
|
|
add_key(found_key);
|
|
live_found = true;
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Key not found in built-in table");
|
|
}
|
|
|
|
// Two-pass dictionary search.
|
|
// When no -f is given: standard pass uses iclass_default_keys.dic,
|
|
// elite pass uses iclass_elite_keys.dic.
|
|
// When -f is given: both passes use the provided file.
|
|
const char *std_file = (fnlen > 0) ? filename : "iclass_default_keys.dic";
|
|
const char *elite_file = (fnlen > 0) ? filename : "iclass_elite_keys.dic";
|
|
|
|
iclass_prekey_t live_lookup;
|
|
memcpy(live_lookup.mac, mac_r, 4);
|
|
|
|
// Standard diversification pass
|
|
if (!live_found) {
|
|
uint8_t *live_keyBlock = NULL;
|
|
uint32_t live_keycount = 0;
|
|
PrintAndLogEx(INFO, "Searching " _YELLOW_("%s") " (standard)...", std_file);
|
|
int res = loadFileDICTIONARY_safe(std_file, (void **)&live_keyBlock, 8, &live_keycount);
|
|
if (res == PM3_SUCCESS && live_keycount > 0) {
|
|
iclass_prekey_t *live_prekey = calloc(live_keycount, sizeof(iclass_prekey_t));
|
|
if (live_prekey == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(live_keyBlock);
|
|
return PM3_EMALLOC;
|
|
}
|
|
GenerateMacKeyFrom(csn, CCNR, false, false, live_keyBlock, live_keycount, live_prekey);
|
|
qsort(live_prekey, live_keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
iclass_prekey_t *live_item = (iclass_prekey_t *) bsearch(&live_lookup, live_prekey, live_keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
if (live_item != NULL) {
|
|
PrintAndLogEx(SUCCESS, "Found standard master key " _GREEN_("%s"), sprint_hex_inrow(live_item->key, 8));
|
|
add_key(live_item->key);
|
|
live_found = true;
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Key not found in %s", std_file);
|
|
}
|
|
free(live_prekey);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed to load dictionary: %s", std_file);
|
|
}
|
|
free(live_keyBlock);
|
|
}
|
|
|
|
// Elite diversification pass
|
|
if (!live_found) {
|
|
uint8_t *live_keyBlock = NULL;
|
|
uint32_t live_keycount = 0;
|
|
PrintAndLogEx(INFO, "Searching " _YELLOW_("%s") " (elite)...", elite_file);
|
|
int res = loadFileDICTIONARY_safe(elite_file, (void **)&live_keyBlock, 8, &live_keycount);
|
|
if (res == PM3_SUCCESS && live_keycount > 0) {
|
|
iclass_prekey_t *live_prekey = calloc(live_keycount, sizeof(iclass_prekey_t));
|
|
if (live_prekey == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(live_keyBlock);
|
|
return PM3_EMALLOC;
|
|
}
|
|
GenerateMacKeyFrom(csn, CCNR, false, true, live_keyBlock, live_keycount, live_prekey);
|
|
qsort(live_prekey, live_keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
iclass_prekey_t *live_item = (iclass_prekey_t *) bsearch(&live_lookup, live_prekey, live_keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
if (live_item != NULL) {
|
|
PrintAndLogEx(SUCCESS, "Found elite master key " _GREEN_("%s"), sprint_hex_inrow(live_item->key, 8));
|
|
add_key(live_item->key);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Key not found in %s", elite_file);
|
|
}
|
|
free(live_prekey);
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed to load dictionary: %s", elite_file);
|
|
}
|
|
free(live_keyBlock);
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
} else {
|
|
if (csn_len != 8 || epurse_len != 8 || macs_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN, ePurse and MACs are required (or use --live)");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// CCNR is epurse || NR (first 4 bytes of macs)
|
|
memcpy(CCNR, epurse, 8);
|
|
memcpy(CCNR + 8, macs, 4);
|
|
memcpy(MAC_TAG, macs + 4, 4);
|
|
|
|
PrintAndLogEx(SUCCESS, "CSN....... " _GREEN_("%s"), sprint_hex(csn, sizeof(csn)));
|
|
PrintAndLogEx(SUCCESS, "Epurse.... %s", sprint_hex(epurse, sizeof(epurse)));
|
|
PrintAndLogEx(SUCCESS, "MACS...... %s", sprint_hex(macs, sizeof(macs)));
|
|
PrintAndLogEx(SUCCESS, "CCNR...... " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
|
|
PrintAndLogEx(SUCCESS, "TAG MAC... %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
|
|
}
|
|
|
|
// Run time
|
|
uint64_t t1 = msclock();
|
|
|
|
uint8_t *keyBlock = NULL;
|
|
uint32_t keycount = 0;
|
|
|
|
if (use_vb6kdf == false) {
|
|
// Load keys
|
|
int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
|
|
if (res != PM3_SUCCESS || keycount == 0) {
|
|
free(keyBlock);
|
|
return res;
|
|
}
|
|
} else {
|
|
// Generate 5000 keys using VB6 KDF
|
|
keycount = 5000;
|
|
keyBlock = calloc(1, keycount * 8);
|
|
if (keyBlock == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
picopass_elite_reset();
|
|
for (uint32_t i = 0; i < keycount; i++) {
|
|
picopass_elite_nextKey(keyBlock + (i * 8));
|
|
}
|
|
}
|
|
|
|
// Iclass_prekey_t
|
|
iclass_prekey_t *prekey = calloc(keycount, sizeof(iclass_prekey_t));
|
|
if (prekey == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(keyBlock);
|
|
return PM3_EMALLOC;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Generating diversified keys...");
|
|
GenerateMacKeyFrom(csn, CCNR, use_raw, use_elite, keyBlock, keycount, prekey);
|
|
|
|
if (use_elite) {
|
|
PrintAndLogEx(INFO, "Using " _YELLOW_("elite algo"));
|
|
}
|
|
|
|
if (use_raw) {
|
|
PrintAndLogEx(INFO, "Using " _YELLOW_("raw mode"));
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Sorting...");
|
|
|
|
// Sort mac list
|
|
qsort(prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
|
|
PrintAndLogEx(SUCCESS, "Searching for %s key...", _YELLOW_("DEBIT"));
|
|
iclass_prekey_t *item;
|
|
iclass_prekey_t lookup;
|
|
memcpy(lookup.mac, MAC_TAG, 4);
|
|
|
|
// Binsearch
|
|
item = (iclass_prekey_t *) bsearch(&lookup, prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);
|
|
|
|
if (item != NULL) {
|
|
PrintAndLogEx(SUCCESS, "Found valid key " _GREEN_("%s"), sprint_hex_inrow(item->key, 8));
|
|
add_key(item->key);
|
|
}
|
|
|
|
t1 = msclock() - t1;
|
|
PrintAndLogEx(SUCCESS, "Time in iclass lookup " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0);
|
|
|
|
free(prekey);
|
|
free(keyBlock);
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
typedef struct {
|
|
uint8_t thread_idx;
|
|
uint8_t use_raw;
|
|
uint8_t use_elite;
|
|
uint32_t keycnt;
|
|
uint8_t csn[PICOPASS_BLOCK_SIZE];
|
|
uint8_t cc_nr[12];
|
|
uint8_t *keys;
|
|
union {
|
|
iclass_premac_t *premac;
|
|
iclass_prekey_t *prekey;
|
|
} list;
|
|
} PACKED iclass_thread_arg_t;
|
|
|
|
static size_t iclass_tc = 1;
|
|
|
|
static pthread_mutex_t generator_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static void *bf_generate_mac(void *thread_arg) {
|
|
|
|
iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
|
|
const uint8_t idx = targ->thread_idx;
|
|
const uint8_t use_raw = targ->use_raw;
|
|
const uint8_t use_elite = targ->use_elite;
|
|
const uint32_t keycnt = targ->keycnt;
|
|
|
|
uint8_t *keys = targ->keys;
|
|
iclass_premac_t *list = targ->list.premac;
|
|
|
|
uint8_t csn[PICOPASS_BLOCK_SIZE];
|
|
uint8_t cc_nr[12];
|
|
memcpy(csn, targ->csn, sizeof(csn));
|
|
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
|
|
|
uint8_t key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t div_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
for (uint32_t i = idx; i < keycnt; i += iclass_tc) {
|
|
|
|
memcpy(key, keys + 8 * i, PICOPASS_BLOCK_SIZE);
|
|
|
|
pthread_mutex_lock(&generator_mutex);
|
|
if (use_raw) {
|
|
memcpy(div_key, key, PICOPASS_BLOCK_SIZE);
|
|
} else {
|
|
HFiClassCalcDivKey(csn, key, div_key, use_elite);
|
|
}
|
|
|
|
doMAC(cc_nr, div_key, list[i].mac);
|
|
pthread_mutex_unlock(&generator_mutex);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// precalc diversified keys and their MAC
|
|
void GenerateMacFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_premac_t *list) {
|
|
|
|
pthread_mutex_init(&generator_mutex, NULL);
|
|
|
|
iclass_tc = num_CPUs();
|
|
pthread_t threads[iclass_tc];
|
|
iclass_thread_arg_t args[iclass_tc];
|
|
// init thread arguments
|
|
for (size_t i = 0; i < iclass_tc; i++) {
|
|
args[i].thread_idx = i;
|
|
args[i].use_raw = use_raw;
|
|
args[i].use_elite = use_elite;
|
|
args[i].keycnt = keycnt;
|
|
args[i].keys = keys;
|
|
args[i].list.premac = list;
|
|
|
|
memcpy(args[i].csn, CSN, sizeof(args[i].csn));
|
|
memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr));
|
|
}
|
|
|
|
for (int i = 0; i < iclass_tc; i++) {
|
|
int res = pthread_create(&threads[i], NULL, bf_generate_mac, (void *)&args[i]);
|
|
if (res) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < iclass_tc; i++) {
|
|
pthread_join(threads[i], NULL);
|
|
}
|
|
}
|
|
|
|
static void *bf_generate_mackey(void *thread_arg) {
|
|
|
|
iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
|
|
const uint8_t idx = targ->thread_idx;
|
|
const uint8_t use_raw = targ->use_raw;
|
|
const uint8_t use_elite = targ->use_elite;
|
|
const uint32_t keycnt = targ->keycnt;
|
|
|
|
uint8_t *keys = targ->keys;
|
|
iclass_prekey_t *list = targ->list.prekey;
|
|
|
|
uint8_t csn[PICOPASS_BLOCK_SIZE];
|
|
uint8_t cc_nr[12];
|
|
memcpy(csn, targ->csn, sizeof(csn));
|
|
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
|
|
|
uint8_t div_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
for (uint32_t i = idx; i < keycnt; i += iclass_tc) {
|
|
|
|
memcpy(list[i].key, keys + 8 * i, PICOPASS_BLOCK_SIZE);
|
|
|
|
pthread_mutex_lock(&generator_mutex);
|
|
if (use_raw) {
|
|
memcpy(div_key, list[i].key, PICOPASS_BLOCK_SIZE);
|
|
} else {
|
|
HFiClassCalcDivKey(csn, list[i].key, div_key, use_elite);
|
|
}
|
|
|
|
doMAC(cc_nr, div_key, list[i].mac);
|
|
pthread_mutex_unlock(&generator_mutex);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void GenerateMacKeyFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_prekey_t *list) {
|
|
|
|
pthread_mutex_init(&generator_mutex, NULL);
|
|
iclass_tc = num_CPUs();
|
|
pthread_t threads[iclass_tc];
|
|
iclass_thread_arg_t args[iclass_tc];
|
|
// init thread arguments
|
|
for (size_t i = 0; i < iclass_tc; i++) {
|
|
args[i].thread_idx = i;
|
|
args[i].use_raw = use_raw;
|
|
args[i].use_elite = use_elite;
|
|
args[i].keycnt = keycnt;
|
|
args[i].keys = keys;
|
|
args[i].list.prekey = list;
|
|
|
|
memcpy(args[i].csn, CSN, sizeof(args[i].csn));
|
|
memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr));
|
|
}
|
|
|
|
for (size_t i = 0; i < iclass_tc; i++) {
|
|
int res = pthread_create(&threads[i], NULL, bf_generate_mackey, (void *)&args[i]);
|
|
if (res) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < iclass_tc; i++) {
|
|
pthread_join(threads[i], NULL);
|
|
}
|
|
}
|
|
|
|
// print diversified keys
|
|
void PrintPreCalcMac(uint8_t *keys, uint32_t keycnt, iclass_premac_t *pre_list) {
|
|
|
|
iclass_prekey_t *b = calloc(keycnt, sizeof(iclass_prekey_t));
|
|
if (b == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < keycnt; i++) {
|
|
memcpy(b[i].key, keys + 8 * i, 8);
|
|
memcpy(b[i].mac, pre_list[i].mac, 4);
|
|
}
|
|
PrintPreCalc(b, keycnt);
|
|
free(b);
|
|
}
|
|
|
|
void PrintPreCalc(iclass_prekey_t *list, uint32_t itemcnt) {
|
|
PrintAndLogEx(NORMAL, "-----+------------------+---------");
|
|
PrintAndLogEx(NORMAL, "#key | key | mac");
|
|
PrintAndLogEx(NORMAL, "-----+------------------+---------");
|
|
for (int i = 0; i < itemcnt; i++) {
|
|
|
|
if (i < 10) {
|
|
PrintAndLogEx(NORMAL, "[%2d] | %016" PRIx64 " | %08" PRIx64, i, bytes_to_num(list[i].key, 8), bytes_to_num(list[i].mac, 4));
|
|
} else if (i == 10) {
|
|
PrintAndLogEx(SUCCESS, "... skip printing the rest");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void permute(uint8_t *data, uint8_t len, uint8_t *output) {
|
|
#define KEY_SIZE 8
|
|
|
|
if (len > KEY_SIZE) {
|
|
for (uint8_t m = 0; m < len; m += KEY_SIZE) {
|
|
permute(data + m, KEY_SIZE, output + m);
|
|
}
|
|
return;
|
|
}
|
|
if (len != KEY_SIZE) {
|
|
PrintAndLogEx(WARNING, "wrong key size\n");
|
|
return;
|
|
}
|
|
for (uint8_t i = 0; i < KEY_SIZE; ++i) {
|
|
uint8_t p = 0;
|
|
uint8_t mask = 0x80 >> i;
|
|
for (uint8_t j = 0; j < KEY_SIZE; ++j) {
|
|
p >>= 1;
|
|
if (data[j] & mask)
|
|
p |= 0x80;
|
|
}
|
|
output[i] = p;
|
|
}
|
|
}
|
|
static void permute_rev(uint8_t *data, uint8_t len, uint8_t *output) {
|
|
permute(data, len, output);
|
|
permute(output, len, data);
|
|
permute(data, len, output);
|
|
}
|
|
static void simple_crc(const uint8_t *data, uint8_t len, uint8_t *output) {
|
|
uint8_t crc = 0;
|
|
for (uint8_t i = 0; i < len; ++i) {
|
|
// seventh byte contains the crc.
|
|
if ((i & 0x7) == 0x7) {
|
|
output[i] = crc ^ 0xFF;
|
|
crc = 0;
|
|
} else {
|
|
output[i] = data[i];
|
|
crc ^= data[i];
|
|
}
|
|
}
|
|
}
|
|
// DES doesn't use the MSB.
|
|
static void shave(uint8_t *data, uint8_t len) {
|
|
for (uint8_t i = 0; i < len; ++i)
|
|
data[i] &= 0xFE;
|
|
}
|
|
static void generate_rev(uint8_t *data, uint8_t len) {
|
|
uint8_t *key = calloc(len, sizeof(uint8_t));
|
|
if (key == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "permuted key..... %s", sprint_hex_inrow(data, len));
|
|
permute_rev(data, len, key);
|
|
PrintAndLogEx(SUCCESS, "unpermuted key... %s", sprint_hex_inrow(key, len));
|
|
shave(key, len);
|
|
PrintAndLogEx(SUCCESS, "key.............. %s", sprint_hex_inrow(key, len));
|
|
free(key);
|
|
}
|
|
static void generate(uint8_t *data, uint8_t len) {
|
|
uint8_t *key = calloc(len, sizeof(uint8_t));
|
|
if (key == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
return;
|
|
}
|
|
|
|
uint8_t *pkey = calloc(len, sizeof(uint8_t));
|
|
if (pkey == NULL) {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
free(key);
|
|
return;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "input key...... %s", sprint_hex_inrow(data, len));
|
|
permute(data, len, pkey);
|
|
PrintAndLogEx(SUCCESS, "permuted key... %s", sprint_hex_inrow(pkey, len));
|
|
simple_crc(pkey, len, key);
|
|
PrintAndLogEx(SUCCESS, "CRC'ed key..... %s", sprint_hex_inrow(key, len));
|
|
free(key);
|
|
free(pkey);
|
|
}
|
|
|
|
static int CmdHFiClassPermuteKey(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass permutekey",
|
|
"Permute function from 'heart of darkness' paper.",
|
|
"hf iclass permutekey --reverse --key 0123456789abcdef\n"
|
|
"hf iclass permutekey --key ff55330f0055330f\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("r", "reverse", "reverse permuted key"),
|
|
arg_str1(NULL, "key", "<hex>", "input key, 8 hex bytes"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
bool isReverse = arg_get_lit(ctx, 1);
|
|
|
|
int dlen = 0;
|
|
uint8_t data[16] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, data, &dlen);
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t key[PICOPASS_BLOCK_SIZE] = {0};
|
|
memcpy(key, data, PICOPASS_BLOCK_SIZE);
|
|
|
|
if (isReverse) {
|
|
generate_rev(data, dlen);
|
|
uint8_t key_std_format[PICOPASS_BLOCK_SIZE] = {0};
|
|
permutekey_rev(key, key_std_format);
|
|
PrintAndLogEx(SUCCESS, "Standard NIST format key..... " _YELLOW_("%s"), sprint_hex_inrow(key_std_format, PICOPASS_BLOCK_SIZE));
|
|
PrintAndLogEx(NORMAL, "");
|
|
} else {
|
|
generate(data, dlen);
|
|
uint8_t key_iclass_format[PICOPASS_BLOCK_SIZE] = {0};
|
|
permutekey(key, key_iclass_format);
|
|
PrintAndLogEx(SUCCESS, "HID permuted iCLASS format... " _YELLOW_("%s"), sprint_hex_inrow(key_iclass_format, PICOPASS_BLOCK_SIZE));
|
|
PrintAndLogEx(NORMAL, "");
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassEncode(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass encode",
|
|
"Encode binary wiegand to block 7,8,9\n"
|
|
"Use either --bin or --wiegand/--fc/--cn\n"
|
|
"Authenticate with either --ki (key slot index) or -k/--key (raw 8-byte hex key)\n"
|
|
"When using emulator you have to first load a credential into emulator memory",
|
|
"hf iclass encode --bin 10001111100000001010100011 --ki 0 -> FC 31 CN 337 (H10301)\n"
|
|
"hf iclass encode -w H10301 --fc 31 --cn 337 --ki 0 -> FC 31 CN 337 (H10301)\n"
|
|
"hf iclass encode -w H10301 --fc 31 --cn 337 -k 0102030405060708 -> authenticate with hex key\n"
|
|
"hf iclass encode --bin 10001111100000001010100011 --ki 0 --elite -> FC 31 CN 337 (H10301), writing w elite key\n"
|
|
"hf iclass encode -w H10301 --fc 31 --cn 337 --emu -> Writes the ecoded data to emulator memory"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "bin", "<bin>", "Binary string i.e 0001001001"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_str0("k", "key", "<hex>", "Key as 8 hex bytes (alternative to --ki)"),
|
|
arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_str0(NULL, "enckey", "<hex>", "3DES transport key, 16 hex bytes"),
|
|
arg_u64_0(NULL, "fc", "<dec>", "facility code"),
|
|
arg_u64_0(NULL, "cn", "<dec>", "card number"),
|
|
arg_u64_0(NULL, "issue", "<dec>", "issue level"),
|
|
arg_str0("w", "wiegand", "<format>", "see " _YELLOW_("`wiegand list`") " for available formats"),
|
|
arg_lit0(NULL, "emu", "Write to emulation memory instead of card"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0("v", NULL, "verbose (print encoded blocks)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
// can only do one block of 8 bytes currently. There are room for two blocks in the specs.
|
|
uint8_t bin[65] = {0};
|
|
int bin_len = sizeof(bin) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated
|
|
CLIGetStrWithReturn(ctx, 1, bin, &bin_len);
|
|
|
|
int key_nr = arg_get_int_def(ctx, 2, -1);
|
|
|
|
int raw_key_len = 0;
|
|
uint8_t raw_key[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, raw_key, &raw_key_len);
|
|
|
|
bool use_emulator_memory = arg_get_lit(ctx, 12);
|
|
|
|
bool auth = false;
|
|
uint8_t key[8] = {0};
|
|
|
|
if (key_nr >= 0 && raw_key_len > 0) {
|
|
PrintAndLogEx(ERR, "Use either --ki or -k/--key, not both");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (raw_key_len > 0 && raw_key_len != 8) {
|
|
PrintAndLogEx(ERR, "Raw key must be 8 bytes (got %d)", raw_key_len);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
// If we use emulator memory skip key requirement
|
|
if (use_emulator_memory == false) {
|
|
if (key_nr < 0 && raw_key_len == 0) {
|
|
PrintAndLogEx(ERR, "Missing required arg for --ki, -k/--key or --emu");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (raw_key_len == 8) {
|
|
auth = true;
|
|
memcpy(key, raw_key, 8);
|
|
PrintAndLogEx(SUCCESS, "Using raw key " _GREEN_("%s"), sprint_hex(key, 8));
|
|
} else if (key_nr >= 0) {
|
|
if (key_nr < ICLASS_KEYS_MAX) {
|
|
auth = true;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool use_credit_key = arg_get_lit(ctx, 4);
|
|
bool elite = arg_get_lit(ctx, 5);
|
|
bool rawkey = arg_get_lit(ctx, 6);
|
|
|
|
int enc_key_len = 0;
|
|
uint8_t enc_key[16] = {0};
|
|
uint8_t *enckeyptr = NULL;
|
|
bool have_enc_key = false;
|
|
bool use_sc = false;
|
|
CLIGetHexWithReturn(ctx, 7, enc_key, &enc_key_len);
|
|
|
|
// FC / CN / Issue Level
|
|
wiegand_card_t card;
|
|
memset(&card, 0, sizeof(wiegand_card_t));
|
|
|
|
card.FacilityCode = arg_get_u32_def(ctx, 8, 0);
|
|
card.CardNumber = arg_get_u32_def(ctx, 9, 0);
|
|
card.IssueLevel = arg_get_u32_def(ctx, 10, 0);
|
|
|
|
char format[16] = {0};
|
|
int format_len = 0;
|
|
|
|
CLIParamStrToBuf(arg_get_str(ctx, 11), (uint8_t *)format, sizeof(format), &format_len);
|
|
|
|
bool shallow_mod = arg_get_lit(ctx, 13);
|
|
bool verbose = arg_get_lit(ctx, 14);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (enc_key_len > 0) {
|
|
if (enc_key_len != 16) {
|
|
PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
|
|
return PM3_EINVARG;
|
|
}
|
|
have_enc_key = true;
|
|
}
|
|
|
|
if (bin_len > 64) {
|
|
PrintAndLogEx(ERR, "Binary wiegand string must be less than 64 bits");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (bin_len == 0 && card.FacilityCode == 0 && card.CardNumber == 0) {
|
|
PrintAndLogEx(ERR, "Must provide either --cn/--fc or --bin");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (have_enc_key == false) {
|
|
// The IsCardHelperPresent function clears the emulator memory
|
|
if (use_emulator_memory) {
|
|
use_sc = false;
|
|
} else {
|
|
use_sc = IsCardHelperPresent(false);
|
|
}
|
|
if (use_sc == false) {
|
|
size_t keylen = 0;
|
|
int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&enckeyptr, &keylen);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(ERR, "Failed to find the transport key");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (keylen != 16) {
|
|
PrintAndLogEx(ERR, "Failed to load transport key from file");
|
|
free(enckeyptr);
|
|
return PM3_EINVARG;
|
|
}
|
|
memcpy(enc_key, enckeyptr, sizeof(enc_key));
|
|
free(enckeyptr);
|
|
}
|
|
}
|
|
|
|
uint8_t credential[] = {
|
|
0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
uint8_t data[8];
|
|
memset(data, 0, sizeof(data));
|
|
BitstreamOut_t bout = {data, 0, 0 };
|
|
|
|
for (int i = 0; i < 64 - bin_len - 1; i++) {
|
|
pushBit(&bout, 0);
|
|
}
|
|
// add binary sentinel bit.
|
|
pushBit(&bout, 1);
|
|
|
|
// convert binary string to hex bytes
|
|
for (int i = 0; i < bin_len; i++) {
|
|
char c = bin[i];
|
|
if (c == '1')
|
|
pushBit(&bout, 1);
|
|
else if (c == '0')
|
|
pushBit(&bout, 0);
|
|
else {
|
|
PrintAndLogEx(WARNING, "Ignoring '%c'", c);
|
|
}
|
|
}
|
|
|
|
if (bin_len) {
|
|
memcpy(credential + 8, data, sizeof(data));
|
|
} else {
|
|
wiegand_message_t packed;
|
|
memset(&packed, 0, sizeof(wiegand_message_t));
|
|
|
|
int format_idx = HIDFindCardFormat(format);
|
|
if (format_idx == -1) {
|
|
PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), format);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (HIDPack(format_idx, &card, &packed, false) == false) {
|
|
PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format.");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
// iceman: only for formats w length smaller than 37.
|
|
// Needs a check.
|
|
|
|
// increase length to allow setting bit just above real data
|
|
packed.Length++;
|
|
// Set sentinel bit
|
|
set_bit_by_position(&packed, true, 0);
|
|
|
|
#ifdef HOST_LITTLE_ENDIAN
|
|
packed.Mid = BSWAP_32(packed.Mid);
|
|
packed.Bot = BSWAP_32(packed.Bot);
|
|
#endif
|
|
|
|
memcpy(credential + 8, &packed.Mid, sizeof(packed.Mid));
|
|
memcpy(credential + 12, &packed.Bot, sizeof(packed.Bot));
|
|
}
|
|
|
|
// encrypt with transport key
|
|
if (use_sc) {
|
|
Encrypt(credential + 8, credential + 8);
|
|
Encrypt(credential + 16, credential + 16);
|
|
Encrypt(credential + 24, credential + 24);
|
|
} else {
|
|
iclass_encrypt_block_data(credential + 8, enc_key);
|
|
iclass_encrypt_block_data(credential + 16, enc_key);
|
|
iclass_encrypt_block_data(credential + 24, enc_key);
|
|
}
|
|
|
|
if (verbose) {
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
PrintAndLogEx(INFO, "Block %d/0x0%x -> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i * 8), 8));
|
|
}
|
|
}
|
|
|
|
if (!g_session.pm3_present) {
|
|
PrintAndLogEx(ERR, "Device offline\n");
|
|
return PM3_EFAILED;
|
|
}
|
|
|
|
int isok = PM3_SUCCESS;
|
|
// write
|
|
if (use_emulator_memory) {
|
|
uint16_t byte_sent = 0;
|
|
iclass_upload_emul(credential, sizeof(credential), 6 * PICOPASS_BLOCK_SIZE, &byte_sent);
|
|
PrintAndLogEx(SUCCESS, "uploaded " _YELLOW_("%d") " bytes to emulator memory", byte_sent);
|
|
PrintAndLogEx(HINT, "Hint: You are now ready to simulate. See `" _YELLOW_("hf iclass sim -h") "`");
|
|
} else {
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
isok = iclass_write_block(6 + i, credential + (i * 8), NULL, key, use_credit_key, elite, rawkey, false, false, auth, shallow_mod);
|
|
switch (isok) {
|
|
case PM3_SUCCESS:
|
|
PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _GREEN_("ok") " ) --> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i * 8), 8));
|
|
break;
|
|
default:
|
|
PrintAndLogEx(INFO, "Write block %d/0x0%x ( " _RED_("fail") " )", 6 + i, 6 + i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return isok;
|
|
}
|
|
|
|
/*
|
|
static int CmdHFiClassAutopwn(const char *Cmd) {
|
|
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass autopwn",
|
|
"Tries to check keys, if found, dump card and save file",
|
|
"hf iclass autopwn\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
CLIParserFree(ctx);
|
|
|
|
// Check keys.
|
|
|
|
// dump
|
|
|
|
PrintAndLogEx(INFO, "to be implemented");
|
|
return PM3_SUCCESS;
|
|
}
|
|
*/
|
|
|
|
static int CmdHFiClassConfigCard(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass configcard",
|
|
"Manage reader configuration card via Cardhelper or internal database,\n"
|
|
"The generated config card will be uploaded to device emulator memory.\n"
|
|
"You can start simulating `hf iclass sim -t 3` or use the emul commands",
|
|
"hf iclass configcard -p --> print all config cards in the database\n"
|
|
"hf iclass configcard --g 0 --> generate config file with option 0"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_int0(NULL, "g", "<dec>", "use config option"),
|
|
arg_int0(NULL, "ki", "<dec>", "Card Key - index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int0(NULL, "eki", "<dec>", "Elite Key - index to select key from memory 'hf iclass managekeys'"),
|
|
arg_int0(NULL, "mrki", "<dec>", "Standard Master Key - index to select key from memory 'hf iclass managekeys'"),
|
|
arg_lit0(NULL, "elite", "Use elite key for the the Card Key ki"),
|
|
arg_lit0("p", NULL, "print available cards"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int ccidx = arg_get_int_def(ctx, 1, -1);
|
|
int card_kidx = arg_get_int_def(ctx, 2, -1);
|
|
int kidx = arg_get_int_def(ctx, 3, -1);
|
|
int midx = arg_get_int_def(ctx, 4, -1);
|
|
bool elite = arg_get_lit(ctx, 5);
|
|
bool do_print = arg_get_lit(ctx, 6);
|
|
CLIParserFree(ctx);
|
|
|
|
bool got_eki = false;
|
|
uint8_t card_key[8] = {0};
|
|
if (card_kidx >= 0) {
|
|
if (card_kidx < ICLASS_KEYS_MAX) {
|
|
got_eki = true;
|
|
memcpy(card_key, iClass_Key_Table[card_kidx], 8);
|
|
PrintAndLogEx(SUCCESS, "Using card key[%d] " _GREEN_("%s"), card_kidx, sprint_hex(iClass_Key_Table[card_kidx], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "--ki number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
bool got_kr = false;
|
|
uint8_t keyroll_key[8] = {0};
|
|
if (kidx >= 0) {
|
|
if (kidx < ICLASS_KEYS_MAX) {
|
|
got_kr = true;
|
|
memcpy(keyroll_key, iClass_Key_Table[kidx], 8);
|
|
PrintAndLogEx(SUCCESS, "Using keyroll key[%d] " _GREEN_("%s"), kidx, sprint_hex(iClass_Key_Table[kidx], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "--eki number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
bool got_mk = false;
|
|
uint8_t master_key[8] = {0};
|
|
if (midx >= 0) {
|
|
if (midx < ICLASS_KEYS_MAX) {
|
|
got_mk = true;
|
|
uint8_t key_iclass_format[8] = {0};
|
|
permutekey(iClass_Key_Table[midx], key_iclass_format);
|
|
memcpy(master_key, key_iclass_format, 8);
|
|
PrintAndLogEx(SUCCESS, "Using key[%d] as new Reader's Master Key" _GREEN_("%s"), midx, sprint_hex(iClass_Key_Table[midx], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "--mrki number is invalid");
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (do_print) {
|
|
print_config_cards();
|
|
}
|
|
|
|
if (ccidx > -1 && ccidx < ARRAYLEN(iclass_config_options)) {
|
|
const iclass_config_card_item_t *item = get_config_card_item(ccidx);
|
|
if (strlen(item->desc) == 0) {
|
|
PrintAndLogEx(ERR, "out of range, %u (got %u)", ARRAYLEN(iclass_config_options), ccidx);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (strstr(item->desc, "ELITE") != NULL && got_kr == false) {
|
|
PrintAndLogEx(ERR, "please specify ELITE Key (--eki) !");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (strstr(item->desc, "Custom") != NULL && got_mk == false) {
|
|
PrintAndLogEx(ERR, "please specify New Standard Master Key (--mrki) !");
|
|
return PM3_EINVARG;
|
|
}
|
|
if (strstr(item->desc, "Restore") != NULL && card_kidx == -1) {
|
|
PrintAndLogEx(ERR, "please specify the Current Reader's Key (--ki) !");
|
|
return PM3_EINVARG;
|
|
}
|
|
generate_config_card(item, keyroll_key, got_kr, card_key, got_eki, elite, got_mk, master_key);
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static bool match_with_wildcard(const uint8_t *data, const uint8_t *pattern, const bool *mask, size_t length) {
|
|
for (size_t i = 0; i < length; ++i) {
|
|
if (mask[i] && data[i] != pattern[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Legacy "hf iclass sam" PACS-extraction implementation.
|
|
//
|
|
// Reached via the CmdHFiClassSAM dispatcher (below) when no SC subcommand
|
|
// keyword is given - so `hf iclass sam`, `hf iclass sam --info`,
|
|
// `hf iclass sam -p -d ...`, `hf iclass sam -f ...` etc. all land here.
|
|
// ---------------------------------------------------------------------------
|
|
static int CmdHFiClassSAMExtract(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sam",
|
|
"Extract PACS via a HID SAM\n",
|
|
"hf iclass sam\n"
|
|
"hf iclass sam -p -d a005a103800104 -> get PACS data, prevent epurse update\n"
|
|
"hf iclass sam --break -> get Nr-MAC for extracting encrypted SIO\n"
|
|
"hf iclass sam -f hf-iclass-dump.bin -> emulate card from dump file to SAM\n"
|
|
"hf iclass sam --info -> get SAM version + serial (also warms up the SAM)\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0("k", "keep", "keep the field active after command executed"),
|
|
arg_lit0("n", "nodetect", "skip selecting the card and sending card details to SAM"),
|
|
arg_lit0("t", "tlv", "decode TLV"),
|
|
arg_lit0(NULL, "break", "stop tag interaction on nr-mac"),
|
|
arg_lit0("p", "prevent", "fake epurse update"),
|
|
arg_lit0(NULL, "shallow", "shallow mod"),
|
|
arg_strx0("d", "data", "<hex>", "DER encoded command to send to SAM"),
|
|
arg_lit0("s", "snmp", "data is in snmp format without headers"),
|
|
arg_lit0(NULL, "info", "get SAM infos (version, serial number)"),
|
|
arg_str0("f", "file", "<fn>", "dump file to emulate to SAM instead of a real card"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
bool disconnect_after = !arg_get_lit(ctx, 2);
|
|
bool skip_detect = arg_get_lit(ctx, 3);
|
|
bool decodeTLV = arg_get_lit(ctx, 4);
|
|
bool break_nrmac = arg_get_lit(ctx, 5);
|
|
bool prevent = arg_get_lit(ctx, 6);
|
|
bool shallow_mod = arg_get_lit(ctx, 7);
|
|
bool snmp_data = arg_get_lit(ctx, 9);
|
|
bool info = arg_get_lit(ctx, 10);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 11), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
bool emulate_from_file = (fnlen > 0);
|
|
|
|
uint8_t flags = 0;
|
|
if (disconnect_after) {
|
|
flags |= BITMASK(0);
|
|
}
|
|
|
|
if (skip_detect) {
|
|
flags |= BITMASK(1);
|
|
}
|
|
|
|
if (break_nrmac) {
|
|
flags |= BITMASK(2);
|
|
}
|
|
|
|
if (prevent) {
|
|
flags |= BITMASK(3);
|
|
}
|
|
|
|
if (shallow_mod) {
|
|
flags |= BITMASK(4);
|
|
}
|
|
|
|
if (info) {
|
|
flags |= BITMASK(5);
|
|
}
|
|
|
|
if (emulate_from_file) {
|
|
flags |= BITMASK(6);
|
|
}
|
|
|
|
uint8_t data[PM3_CMD_DATA_SIZE] = {0};
|
|
data[0] = flags;
|
|
|
|
int cmdlen = 0;
|
|
if (CLIParamHexToBuf(arg_get_str(ctx, 8), data + 1, PM3_CMD_DATA_SIZE - 1, &cmdlen) != PM3_SUCCESS) {
|
|
CLIParserFree(ctx);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if (IsHIDSamPresent(verbose) == false) {
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (emulate_from_file) {
|
|
// Load dump and upload to ARM emulator memory
|
|
uint8_t *dump = NULL;
|
|
size_t bytes_read = 2048;
|
|
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Loaded %zu bytes from " _YELLOW_("%s"), bytes_read, filename);
|
|
|
|
uint16_t bytes_sent = 0;
|
|
iclass_upload_emul(dump, bytes_read, 0, &bytes_sent);
|
|
free(dump);
|
|
PrintAndLogEx(SUCCESS, "Uploaded " _YELLOW_("%u") " bytes to emulator memory", bytes_sent);
|
|
}
|
|
|
|
if (snmp_data) {
|
|
uint8_t header[4] = {0xa0, cmdlen + 2, 0x94, cmdlen };
|
|
memmove(data + 4, data, cmdlen + 1);
|
|
data[0] = flags;
|
|
memcpy(data + 1, header, 4);
|
|
cmdlen += 4;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_SAM_PICOPASS, data, cmdlen + 1);
|
|
PacketResponseNG resp;
|
|
WaitForResponse(CMD_HF_SAM_PICOPASS, &resp);
|
|
|
|
bool is_snmp = false;
|
|
uint8_t snmp_pattern[] = {0xBD, 0x81, 0xFF, 0x8A, 0x81, 0xFF}; // SNMP Response header pattern, 0xFF is a wildcard value for message length
|
|
bool snmp_mask[] = {true, true, false, true, true, false}; // false means wildcard value in that position
|
|
uint8_t ok_pattern[] = {0xBD, 0xFF, 0x8A}; // Ok response header pattern, 0xFF is a wildcard value for message length
|
|
bool ok_mask[] = {true, false, true}; // false means wildcard value in that position
|
|
|
|
switch (resp.status) {
|
|
case PM3_SUCCESS:
|
|
break;
|
|
case PM3_ENOPACS:
|
|
PrintAndLogEx(SUCCESS, "No PACS data found. Card empty?");
|
|
return resp.status;
|
|
default:
|
|
PrintAndLogEx(WARNING, "SAM select failed");
|
|
return resp.status;
|
|
}
|
|
|
|
uint8_t *d = resp.data.asBytes;
|
|
// check for standard SamCommandGetContentElement response
|
|
// bd 09
|
|
// 8a 07
|
|
// 03 05 <- tag + length
|
|
// 06 85 80 6d c0 <- decoded PACS data
|
|
if (d[0] == 0xbd && d[2] == 0x8a && d[4] == 0x03) {
|
|
uint8_t pacs_length = d[5];
|
|
uint8_t *pacs_data = d + 6;
|
|
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
// check for standard samCommandGetContentElement2:
|
|
// bd 1e
|
|
// b3 1c
|
|
// a0 1a
|
|
// 80 05
|
|
// 06 85 80 6d c0
|
|
// 81 0e
|
|
// 2b 06 01 04 01 81 e4 38 01 01 02 04 3c ff
|
|
// 82 01
|
|
// 07
|
|
} else if (d[0] == 0xbd && d[2] == 0xb3 && d[4] == 0xa0) {
|
|
const uint8_t *pacs = d + 6;
|
|
const uint8_t pacs_length = pacs[1];
|
|
const uint8_t *pacs_data = pacs + 2;
|
|
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
const uint8_t *oid = pacs + 2 + pacs_length;
|
|
const uint8_t oid_length = oid[1];
|
|
const uint8_t *oid_data = oid + 2;
|
|
PrintAndLogEx(SUCCESS, "SIO OID.......... " _GREEN_("%s"), sprint_hex_inrow(oid_data, oid_length));
|
|
|
|
const uint8_t *mediaType = oid + 2 + oid_length;
|
|
const uint8_t mediaType_data = mediaType[2];
|
|
PrintAndLogEx(SUCCESS, "SIO Media Type... " _GREEN_("%s"), getSioMediaTypeInfo(mediaType_data));
|
|
} else if (break_nrmac && d[0] == 0x05) {
|
|
PrintAndLogEx(SUCCESS, "Nr-MAC........... " _GREEN_("%s"), sprint_hex_inrow(d + 1, 8));
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Replay Nr-MAC to dump SIO:");
|
|
PrintAndLogEx(SUCCESS, " hf iclass dump --nr -k %s", sprint_hex_inrow(d + 1, 8));
|
|
}
|
|
} else {
|
|
//if it is an error decode it
|
|
if (memcmp(d, "\xBE\x07\x80\x01", 4) == 0) { //if it the string is 0xbe 0x07 0x80 0x01 the next byte will indicate the error code
|
|
PrintAndLogEx(ERR, _RED_("Sam Error Code: %02x"), d[4]);
|
|
print_hex(d, resp.length);
|
|
} else if (match_with_wildcard(d, snmp_pattern, snmp_mask, 6)) {
|
|
is_snmp = true;
|
|
PrintAndLogEx(SUCCESS, _YELLOW_("[samSNMPMessageResponse] ")"%s", sprint_hex(d + 6, resp.length - 6));
|
|
} else if (match_with_wildcard(d, ok_pattern, ok_mask, 3)) {
|
|
PrintAndLogEx(SUCCESS, _YELLOW_("[samResponseAcknowledge] ")"%s", sprint_hex(d + 4, resp.length - 4));
|
|
} else {
|
|
print_hex(d, resp.length);
|
|
}
|
|
}
|
|
|
|
if (decodeTLV && is_snmp == false) {
|
|
asn1_print(d, d[1] + 2, " ");
|
|
} else if (decodeTLV && is_snmp) {
|
|
asn1_print(d + 6, resp.length - 6, " ");
|
|
}
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// HID Artemis secure-channel session - persistent across CLI calls
|
|
// ===========================================================================
|
|
//
|
|
// Sits on top of the new CMD_HF_SAM_SC firmware dispatcher (armsrc/sam_sc.c).
|
|
// The firmware is just a transport pipe - host owns the SCP02 / Grace crypto
|
|
// state (sEnc / sMAC1 / sMAC2 / rolling C-MAC + R-MAC) and the SAM-assigned
|
|
// scFlag. State persists across CLI invocations in the static s_sam_sc.
|
|
//
|
|
// All bit patterns mirror UTILITIES_TOOLS/cp1000_client/secure_channel.py.
|
|
|
|
// Mirror of armsrc/sam_sc.h - duplicated locally so the client doesn't have
|
|
// to pull armsrc/ headers. Keep in sync if those flags ever change.
|
|
#define SAM_SC_FLAG_FORCE_RESET (1 << 0)
|
|
#define SAM_SC_FLAG_RELEASE (1 << 1)
|
|
#define SAM_SC_FLAG_NO_PAYLOAD (1 << 2)
|
|
|
|
// Mirror of armsrc/i2c.h::ISO7816_MAX_FRAME (270). Not exported to the
|
|
// client; used as an upper bound for SC plaintext / wrap buffers.
|
|
#define SAM_SC_MAX_FRAME 270
|
|
|
|
// All-zero placeholder master key. Real master keys depend on the target
|
|
// SAM's provisioning state and must be supplied with --key. Slot 0x85 is the
|
|
// canonical HidUserAdmin slot index on CP1000-class encoders.
|
|
static const uint8_t SAM_SC_DEFAULT_MASTER_KEY[16] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
static const uint8_t SAM_SC_DEFAULT_KREF = 0x85;
|
|
|
|
typedef struct {
|
|
bool open;
|
|
uint8_t kref;
|
|
uint8_t master_key[16];
|
|
uint8_t suid[8];
|
|
uint8_t rnd_a[8];
|
|
uint8_t rnd_b[8];
|
|
uint8_t scbk[16];
|
|
uint8_t s_enc[16];
|
|
uint8_t s_mac1[16];
|
|
uint8_t s_mac2[16];
|
|
uint8_t c_mac[16]; // rolling client MAC (advanced by Wrap)
|
|
uint8_t r_mac[16]; // rolling server MAC (advanced by Unwrap)
|
|
uint8_t sc_flag; // SAM-assigned secure-channel routing byte
|
|
} sam_sc_session_t;
|
|
|
|
static sam_sc_session_t s_sam_sc = {0};
|
|
|
|
// HID's custom CBC-MAC variant. Zero-pad msg to a 16B multiple; AES-ECB
|
|
// the first N-1 blocks with sMAC1, the last with sMAC2. Initial mac = iv.
|
|
// Output is 16 bytes (no truncation). AES-ECB single block done via
|
|
// aes_encode() with a zero CBC-IV.
|
|
static void sam_sc_hid_mac(const uint8_t s_mac1[16], const uint8_t s_mac2[16],
|
|
const uint8_t iv[16],
|
|
const uint8_t *msg, size_t msg_len,
|
|
uint8_t out[16]) {
|
|
uint8_t mac[16];
|
|
memcpy(mac, iv, 16);
|
|
|
|
if (msg_len == 0) msg_len = 16;
|
|
size_t pad = (16 - (msg_len % 16)) % 16;
|
|
size_t total = msg_len + pad;
|
|
size_t blocks = total / 16;
|
|
|
|
for (size_t i = 0; i < blocks; i++) {
|
|
uint8_t block[16];
|
|
size_t off = i * 16;
|
|
size_t copy = (off + 16 <= msg_len) ? 16 : (msg_len > off ? msg_len - off : 0);
|
|
if (copy > 0) memcpy(block, msg + off, copy);
|
|
if (copy < 16) memset(block + copy, 0x00, 16 - copy);
|
|
|
|
for (size_t j = 0; j < 16; j++) block[j] ^= mac[j];
|
|
|
|
uint8_t zero_iv[16] = {0}, key_copy[16];
|
|
memcpy(key_copy, (i + 1 == blocks) ? s_mac2 : s_mac1, 16);
|
|
aes_encode(zero_iv, key_copy, block, mac, 16);
|
|
}
|
|
memcpy(out, mac, 16);
|
|
}
|
|
|
|
// Send a payload via CMD_HF_SAM_SC. Returns SAM-assigned scFlag and the SAM
|
|
// response bytes (BD/BE-prefixed) per the reply layout in armsrc/sam_sc.h.
|
|
static int sam_sc_dispatch(uint8_t flags, uint8_t scflag_in,
|
|
const uint8_t *payload, uint16_t payload_len,
|
|
uint8_t *sc_flag_out, uint8_t *sam_resp,
|
|
uint16_t *sam_resp_len) {
|
|
if (sc_flag_out == NULL || sam_resp == NULL || sam_resp_len == NULL)
|
|
return PM3_EINVARG;
|
|
if ((size_t)payload_len + 5 > PM3_CMD_DATA_SIZE)
|
|
return PM3_EINVARG;
|
|
|
|
uint8_t pkt[PM3_CMD_DATA_SIZE] = {0};
|
|
pkt[0] = flags;
|
|
pkt[1] = 0x44; // ipcNodeIdExternalApplicationA (us)
|
|
pkt[2] = 0x0A; // ipcNodeIdPrimarySam
|
|
pkt[3] = 0x44; // reply-to = us
|
|
pkt[4] = scflag_in;
|
|
if (payload_len > 0)
|
|
memcpy(pkt + 5, payload, payload_len);
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_SAM_SC, pkt, payload_len + 5);
|
|
|
|
PacketResponseNG resp;
|
|
if (WaitForResponse(CMD_HF_SAM_SC, &resp) == false)
|
|
return PM3_ETIMEOUT;
|
|
if (resp.status != PM3_SUCCESS)
|
|
return resp.status;
|
|
if (resp.length < 1)
|
|
return PM3_ESOFT;
|
|
|
|
*sc_flag_out = resp.data.asBytes[0];
|
|
uint16_t body_len = (uint16_t)(resp.length - 1);
|
|
if (body_len > *sam_resp_len) body_len = *sam_resp_len;
|
|
if (body_len > 0) memcpy(sam_resp, resp.data.asBytes + 1, body_len);
|
|
*sam_resp_len = body_len;
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// Build the InitAuth SAM payload (23 bytes):
|
|
// A0 15 AF 13 80 01 <ver> 81 01 <kref> 82 08 <rnd_a> 83 01 <tca>
|
|
static uint16_t sam_sc_build_init_auth(uint8_t kref, const uint8_t rnd_a[8],
|
|
uint8_t out[23]) {
|
|
out[0] = 0xA0;
|
|
out[1] = 0x15;
|
|
out[2] = 0xAF;
|
|
out[3] = 0x13;
|
|
out[4] = 0x80;
|
|
out[5] = 0x01;
|
|
out[6] = 0x00; // version=0
|
|
out[7] = 0x81;
|
|
out[8] = 0x01;
|
|
out[9] = kref;
|
|
out[10] = 0x82;
|
|
out[11] = 0x08;
|
|
memcpy(out + 12, rnd_a, 8);
|
|
out[20] = 0x83;
|
|
out[21] = 0x01;
|
|
out[22] = 0x00; // tca=0
|
|
return 23;
|
|
}
|
|
|
|
// Build the ContinueAuth SAM payload (40 bytes):
|
|
// A0 26 B0 24 80 10 <clientCryptogram> 81 10 <clientCmac>
|
|
static uint16_t sam_sc_build_continue_auth(const uint8_t client_crypto[16],
|
|
const uint8_t client_cmac[16],
|
|
uint8_t out[40]) {
|
|
out[0] = 0xA0;
|
|
out[1] = 0x26;
|
|
out[2] = 0xB0;
|
|
out[3] = 0x24;
|
|
out[4] = 0x80;
|
|
out[5] = 0x10;
|
|
memcpy(out + 6, client_crypto, 16);
|
|
out[22] = 0x81;
|
|
out[23] = 0x10;
|
|
memcpy(out + 24, client_cmac, 16);
|
|
return 40;
|
|
}
|
|
|
|
// out = AES-CBC(scbk, IV=0, prefix(2) || rnd_b[0..2] || zeros[12]) (1 block).
|
|
static void sam_sc_kdf_derive(const uint8_t scbk[16], const uint8_t prefix[2],
|
|
const uint8_t rnd_b[8], uint8_t out[16]) {
|
|
uint8_t in[16];
|
|
in[0] = prefix[0];
|
|
in[1] = prefix[1];
|
|
in[2] = rnd_b[0];
|
|
in[3] = rnd_b[1];
|
|
memset(in + 4, 0, 12);
|
|
uint8_t iv[16] = {0}, key_copy[16];
|
|
memcpy(key_copy, scbk, 16);
|
|
aes_encode(iv, key_copy, in, out, 16);
|
|
}
|
|
|
|
// Wrap plaintext for transmission on the open SC. Returns wrapped length.
|
|
// padded = plaintext || 0x80 || zeros (to next 16B)
|
|
// iv_enc = ~r_mac
|
|
// ciphertext = AES-CBC(s_enc, iv_enc, padded)
|
|
// new_cmac = HID-MAC(s_mac1, s_mac2, IV=r_mac, ciphertext)
|
|
// c_mac <- new_cmac (R-MAC unchanged; only Unwrap advances it)
|
|
// return ciphertext || new_cmac
|
|
static uint16_t sam_sc_wrap(const uint8_t *plaintext, uint16_t plaintext_len,
|
|
uint8_t *out, uint16_t out_cap) {
|
|
if (s_sam_sc.open == false) return 0;
|
|
uint16_t padded_len = plaintext_len + 1;
|
|
if ((padded_len % 16) != 0) padded_len += 16 - (padded_len % 16);
|
|
if ((uint32_t)padded_len + 16 > out_cap) return 0;
|
|
|
|
uint8_t padded[SAM_SC_MAX_FRAME];
|
|
if (padded_len > sizeof(padded)) return 0;
|
|
if (plaintext_len > 0) memcpy(padded, plaintext, plaintext_len);
|
|
padded[plaintext_len] = 0x80;
|
|
if (padded_len > plaintext_len + 1)
|
|
memset(padded + plaintext_len + 1, 0x00, padded_len - plaintext_len - 1);
|
|
|
|
uint8_t iv_enc[16], key_copy[16];
|
|
for (int i = 0; i < 16; i++) iv_enc[i] = (uint8_t)(s_sam_sc.r_mac[i] ^ 0xFF);
|
|
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
|
aes_encode(iv_enc, key_copy, padded, out, padded_len);
|
|
|
|
uint8_t new_cmac[16];
|
|
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, s_sam_sc.r_mac,
|
|
out, padded_len, new_cmac);
|
|
|
|
memcpy(out + padded_len, new_cmac, 16);
|
|
memcpy(s_sam_sc.c_mac, new_cmac, 16);
|
|
return (uint16_t)(padded_len + 16);
|
|
}
|
|
|
|
// Unwrap ciphertext+MAC. Verifies MAC under c_mac, decrypts under sEnc with
|
|
// iv=~c_mac, strips 0x80+zeros padding, advances r_mac. Returns plaintext
|
|
// length on success, -1 on MAC mismatch or padding error.
|
|
static int sam_sc_unwrap(const uint8_t *data, uint16_t data_len,
|
|
uint8_t *plaintext_out, uint16_t out_cap) {
|
|
if (s_sam_sc.open == false) return -1;
|
|
if (data_len < 16) return -1;
|
|
uint16_t ct_len = data_len - 16;
|
|
const uint8_t *ciphertext = data;
|
|
const uint8_t *received_mac = data + ct_len;
|
|
|
|
uint8_t expected_mac[16];
|
|
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, s_sam_sc.c_mac,
|
|
ciphertext, ct_len, expected_mac);
|
|
if (memcmp(expected_mac, received_mac, 16) != 0) return -1;
|
|
|
|
if (ct_len == 0) {
|
|
memcpy(s_sam_sc.r_mac, received_mac, 16);
|
|
return 0;
|
|
}
|
|
if (ct_len > out_cap) return -1;
|
|
|
|
uint8_t iv_dec[16], key_copy[16];
|
|
for (int i = 0; i < 16; i++) iv_dec[i] = (uint8_t)(s_sam_sc.c_mac[i] ^ 0xFF);
|
|
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
|
aes_decode(iv_dec, key_copy, (uint8_t *)ciphertext, plaintext_out, ct_len);
|
|
|
|
int i = ct_len - 1;
|
|
while (i >= 0 && plaintext_out[i] == 0x00) i--;
|
|
if (i < 0 || plaintext_out[i] != 0x80) return -1;
|
|
int plaintext_len = i;
|
|
|
|
memcpy(s_sam_sc.r_mac, received_mac, 16);
|
|
return plaintext_len;
|
|
}
|
|
|
|
// Peel BD <len> 8A <len> or BD <len> B3 <len> or BE <len> envelopes.
|
|
// Returns pointer to inner ciphertext+MAC bytes + its length. *path is set
|
|
// to 'A', 'B', or 'C'. Returns 0 on success, -1 on shape error. Supports
|
|
// short-form and long-form (0x81/0x82) BER lengths.
|
|
static int sam_sc_peel_envelope(const uint8_t *resp, uint16_t resp_len,
|
|
const uint8_t **inner, uint16_t *inner_len,
|
|
char *path) {
|
|
if (resp_len < 2) return -1;
|
|
|
|
#define SAM_SC_READ_BER_LEN(buf, buflen, off, out_len) \
|
|
do { \
|
|
if ((off) >= (buflen)) return -1; \
|
|
uint8_t b0 = (buf)[(off)++]; \
|
|
if (b0 < 0x80) { (out_len) = b0; } \
|
|
else if (b0 == 0x81) { \
|
|
if ((off) >= (buflen)) return -1; \
|
|
(out_len) = (buf)[(off)++]; \
|
|
} else if (b0 == 0x82) { \
|
|
if ((off) + 1 >= (buflen)) return -1; \
|
|
(out_len) = ((uint16_t)(buf)[(off)] << 8) | (buf)[(off)+1]; \
|
|
(off) += 2; \
|
|
} else return -1; \
|
|
} while (0)
|
|
|
|
uint16_t off = 0;
|
|
uint8_t outer_tag = resp[off++];
|
|
uint16_t outer_len;
|
|
SAM_SC_READ_BER_LEN(resp, resp_len, off, outer_len);
|
|
if ((uint32_t)off + outer_len > resp_len) return -1;
|
|
|
|
if (outer_tag == 0xBE) {
|
|
*path = 'C';
|
|
*inner = resp + off;
|
|
*inner_len = outer_len;
|
|
return 0;
|
|
}
|
|
if (outer_tag != 0xBD) return -1;
|
|
|
|
if (off >= resp_len) return -1;
|
|
uint8_t inner_tag = resp[off++];
|
|
uint16_t in_len;
|
|
SAM_SC_READ_BER_LEN(resp, resp_len, off, in_len);
|
|
if ((uint32_t)off + in_len > resp_len) return -1;
|
|
|
|
if (inner_tag == 0x8A) *path = 'A';
|
|
else if (inner_tag == 0xB3) *path = 'B';
|
|
else return -1;
|
|
|
|
*inner = resp + off;
|
|
*inner_len = in_len;
|
|
return 0;
|
|
|
|
#undef SAM_SC_READ_BER_LEN
|
|
}
|
|
|
|
// Encode a BER length (short form < 128, otherwise 0x81/0x82).
|
|
static uint8_t sam_sc_emit_ber_len(uint8_t *out, uint16_t len) {
|
|
if (len < 0x80) { out[0] = (uint8_t)len; return 1; }
|
|
if (len < 0x100) { out[0] = 0x81; out[1] = (uint8_t)len; return 2; }
|
|
out[0] = 0x82;
|
|
out[1] = (uint8_t)(len >> 8);
|
|
out[2] = (uint8_t)(len & 0xFF);
|
|
return 3;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf iclass sam scopen
|
|
// ---------------------------------------------------------------------------
|
|
static int CmdHFiClassSAMSCOpen(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sam scopen",
|
|
"Open a HID Artemis secure channel: InitAuth + ContinueAuth.\n"
|
|
"Persists the session state across CLI commands so subsequent\n"
|
|
"scsend / scclose operations can wrap/unwrap APDUs against\n"
|
|
"the same SAM-side session.\n"
|
|
"Defaults: --key 00000000000000000000000000000000 --kref 0x85",
|
|
"hf iclass sam scopen --key 00000000000000000000000000000000\n");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0(NULL, "key", "<hex>", "16-byte AES master key (default: all zeros - override with real key)"),
|
|
arg_int0(NULL, "kref", "<dec>", "key reference slot 0..255 (default: 0x85)"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
uint8_t master_key[16];
|
|
int key_len = 0;
|
|
if (CLIParamHexToBuf(arg_get_str(ctx, 1), master_key, sizeof(master_key), &key_len) != PM3_SUCCESS) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (key_len == 0) {
|
|
memcpy(master_key, SAM_SC_DEFAULT_MASTER_KEY, 16);
|
|
} else if (key_len != 16) {
|
|
PrintAndLogEx(FAILED, "--key must be exactly 16 bytes (32 hex chars), got %d", key_len);
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
int kref = arg_get_int_def(ctx, 2, SAM_SC_DEFAULT_KREF);
|
|
bool verbose = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
if (kref < 0 || kref > 0xFF) {
|
|
PrintAndLogEx(FAILED, "--kref must be 0..255");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
memcpy(s_sam_sc.master_key, master_key, 16);
|
|
s_sam_sc.kref = (uint8_t)kref;
|
|
|
|
PrintAndLogEx(INFO, "KREF : 0x%02X", s_sam_sc.kref);
|
|
PrintAndLogEx(INFO, "Master key : %s", sprint_hex_inrow(master_key, 16));
|
|
|
|
// ---------------- Initialize the SAM ----------------
|
|
// Run the equivalent of `hf iclass sam --info` first. This:
|
|
// - Resets the SAM via I2C (CMD_HF_SAM_PICOPASS dispatcher)
|
|
// - Sends sam_get_version + sam_get_serial_number as a warmup
|
|
// - Prints the version + serial to the user
|
|
// The InitAuth that follows then reaches a SAM in a known clean state.
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "--- Initializing SAM ('hf iclass sam --info') ---");
|
|
int init_rc = CmdHFiClassSAMExtract("--info");
|
|
if (init_rc != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "SAM init returned rc=%d; continuing with InitAuth anyway", init_rc);
|
|
}
|
|
PrintAndLogEx(INFO, "--- Opening secure channel ---");
|
|
PrintAndLogEx(INFO, "");
|
|
|
|
// ---------------- InitAuth ----------------
|
|
uint8_t init_auth[23];
|
|
sam_sc_build_init_auth(s_sam_sc.kref, s_sam_sc.rnd_a, init_auth);
|
|
|
|
uint8_t resp[PM3_CMD_DATA_SIZE];
|
|
uint16_t resp_len = sizeof(resp);
|
|
uint8_t sam_flag = 0;
|
|
int rc = sam_sc_dispatch(SAM_SC_FLAG_FORCE_RESET, 0x00,
|
|
init_auth, sizeof(init_auth),
|
|
&sam_flag, resp, &resp_len);
|
|
if (rc != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "InitAuth dispatch failed (rc=%d)", rc);
|
|
return rc;
|
|
}
|
|
if (verbose)
|
|
PrintAndLogEx(DEBUG, "InitAuth scFlag=0x%02X resp(%u): %s",
|
|
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
|
|
|
if (resp_len < 36) {
|
|
PrintAndLogEx(FAILED, "InitAuth response too short (%u bytes)", resp_len);
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
|
return PM3_ESOFT;
|
|
}
|
|
if (resp[0] != 0xBD || resp[2] != 0x8A || resp[3] != 0x20) {
|
|
PrintAndLogEx(FAILED, "InitAuth response shape unexpected: %02X %02X %02X %02X ...",
|
|
resp[0], resp[1], resp[2], resp[3]);
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(s_sam_sc.suid, resp + 4, 8);
|
|
memcpy(s_sam_sc.rnd_b, resp + 12, 8);
|
|
uint8_t server_cryptogram[16];
|
|
memcpy(server_cryptogram, resp + 20, 16);
|
|
s_sam_sc.sc_flag = sam_flag;
|
|
|
|
PrintAndLogEx(INFO, "scFlag : 0x%02X (SAM-assigned)", s_sam_sc.sc_flag);
|
|
PrintAndLogEx(INFO, "Server UID : %s", sprint_hex_inrow(s_sam_sc.suid, 8));
|
|
PrintAndLogEx(INFO, "Server RNDB: %s", sprint_hex_inrow(s_sam_sc.rnd_b, 8));
|
|
PrintAndLogEx(INFO, "ServerCrypt: %s", sprint_hex_inrow(server_cryptogram, 16));
|
|
|
|
// ---------------- Derive SCBK + session keys ----------------
|
|
{
|
|
uint8_t suid_data[16];
|
|
memcpy(suid_data, s_sam_sc.suid, 8);
|
|
for (int i = 0; i < 8; i++) suid_data[8 + i] = (uint8_t)(s_sam_sc.suid[i] ^ 0xFF);
|
|
uint8_t iv[16] = {0}, key_copy[16];
|
|
memcpy(key_copy, master_key, 16);
|
|
aes_encode(iv, key_copy, suid_data, s_sam_sc.scbk, 16);
|
|
}
|
|
static const uint8_t SC_PFX_MAC1[2] = {0x01, 0x01};
|
|
static const uint8_t SC_PFX_MAC2[2] = {0x01, 0x02};
|
|
static const uint8_t SC_PFX_ENC[2] = {0x01, 0x82};
|
|
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_MAC1, s_sam_sc.rnd_b, s_sam_sc.s_mac1);
|
|
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_MAC2, s_sam_sc.rnd_b, s_sam_sc.s_mac2);
|
|
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_ENC, s_sam_sc.rnd_b, s_sam_sc.s_enc);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "SCBK : %s", sprint_hex_inrow(s_sam_sc.scbk, 16));
|
|
PrintAndLogEx(DEBUG, "sEnc : %s", sprint_hex_inrow(s_sam_sc.s_enc, 16));
|
|
PrintAndLogEx(DEBUG, "sMAC1: %s", sprint_hex_inrow(s_sam_sc.s_mac1, 16));
|
|
PrintAndLogEx(DEBUG, "sMAC2: %s", sprint_hex_inrow(s_sam_sc.s_mac2, 16));
|
|
}
|
|
|
|
uint8_t expected_srv[16];
|
|
{
|
|
uint8_t in[16], iv[16] = {0}, key_copy[16];
|
|
memcpy(in, s_sam_sc.rnd_a, 8);
|
|
memcpy(in + 8, s_sam_sc.rnd_b, 8);
|
|
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
|
aes_encode(iv, key_copy, in, expected_srv, 16);
|
|
}
|
|
if (memcmp(expected_srv, server_cryptogram, 16) != 0) {
|
|
PrintAndLogEx(FAILED, _RED_("Server cryptogram mismatch") " - master key wrong for KREF 0x%02X",
|
|
s_sam_sc.kref);
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "expected: %s", sprint_hex_inrow(expected_srv, 16));
|
|
PrintAndLogEx(DEBUG, "got : %s", sprint_hex_inrow(server_cryptogram, 16));
|
|
}
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return PM3_ESOFT;
|
|
}
|
|
PrintAndLogEx(SUCCESS, _GREEN_("InitAuth OK") " - server cryptogram verified");
|
|
|
|
// ---------------- ContinueAuth ----------------
|
|
uint8_t client_crypto[16];
|
|
{
|
|
uint8_t in[16], iv[16] = {0}, key_copy[16];
|
|
memcpy(in, s_sam_sc.rnd_b, 8);
|
|
memcpy(in + 8, s_sam_sc.rnd_a, 8);
|
|
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
|
aes_encode(iv, key_copy, in, client_crypto, 16);
|
|
}
|
|
uint8_t client_cmac[16];
|
|
{
|
|
uint8_t iv0[16] = {0};
|
|
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, iv0, client_crypto, 16, client_cmac);
|
|
}
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "client_crypto: %s", sprint_hex_inrow(client_crypto, 16));
|
|
PrintAndLogEx(DEBUG, "client_cmac : %s", sprint_hex_inrow(client_cmac, 16));
|
|
}
|
|
|
|
uint8_t cont_auth[40];
|
|
sam_sc_build_continue_auth(client_crypto, client_cmac, cont_auth);
|
|
|
|
resp_len = sizeof(resp);
|
|
rc = sam_sc_dispatch(0, s_sam_sc.sc_flag, cont_auth, sizeof(cont_auth),
|
|
&sam_flag, resp, &resp_len);
|
|
if (rc != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "ContinueAuth dispatch failed (rc=%d)", rc);
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return rc;
|
|
}
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "ContinueAuth scFlag=0x%02X resp(%u): %s",
|
|
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
|
}
|
|
if (resp_len < 20) {
|
|
PrintAndLogEx(FAILED, "ContinueAuth response too short (%u bytes)", resp_len);
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return PM3_ESOFT;
|
|
}
|
|
if (resp[0] != 0xBD || resp[2] != 0x8A || resp[3] != 0x10) {
|
|
PrintAndLogEx(FAILED, "ContinueAuth response shape unexpected: %02X %02X %02X %02X ...",
|
|
resp[0], resp[1], resp[2], resp[3]);
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return PM3_ESOFT;
|
|
}
|
|
uint8_t server_rmac[16];
|
|
memcpy(server_rmac, resp + 4, 16);
|
|
|
|
uint8_t expected_rmac[16];
|
|
{
|
|
uint8_t pad_block[16] = {0};
|
|
pad_block[0] = 0x80;
|
|
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, client_cmac,
|
|
pad_block, 16, expected_rmac);
|
|
}
|
|
if (memcmp(expected_rmac, server_rmac, 16) != 0) {
|
|
PrintAndLogEx(FAILED, _RED_("ContinueAuth R-MAC mismatch") " - SAM did not authenticate");
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "expected: %s", sprint_hex_inrow(expected_rmac, 16));
|
|
PrintAndLogEx(DEBUG, "got : %s", sprint_hex_inrow(server_rmac, 16));
|
|
}
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
memcpy(s_sam_sc.c_mac, client_cmac, 16);
|
|
memcpy(s_sam_sc.r_mac, server_rmac, 16);
|
|
s_sam_sc.sc_flag = sam_flag;
|
|
s_sam_sc.open = true;
|
|
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Secure channel OPEN") " (scFlag=0x%02X, KREF=0x%02X)",
|
|
s_sam_sc.sc_flag, s_sam_sc.kref);
|
|
PrintAndLogEx(INFO, "Use 'hf iclass sam scsend' to send wrapped APDUs;");
|
|
PrintAndLogEx(INFO, "use 'hf iclass sam scclose' to terminate.");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf iclass sam scsend --payload <hex>
|
|
// ---------------------------------------------------------------------------
|
|
static int CmdHFiClassSAMSCSend(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sam scsend",
|
|
"Send a plaintext SAMCommand body through the open secure\n"
|
|
"channel. The client wraps it (encrypt + MAC + chain RMAC),\n"
|
|
"sends via CMD_HF_SAM_SC, unwraps the response, and prints\n"
|
|
"the plaintext. Requires 'scopen' to have been called first.\n"
|
|
"Test payload: --payload 8200 (samCommandGetSamVersion)",
|
|
"hf iclass sam scsend --payload 8200\n"
|
|
"hf iclass sam scsend --payload b9028a00 -v\n"
|
|
"hf iclass sam scsend --payload b903850100");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "payload", "<hex>", "plaintext SAMCommand body (the bytes inside A0 <len>)"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
if (s_sam_sc.open == false) {
|
|
PrintAndLogEx(FAILED, "no open secure channel - run 'scopen' first");
|
|
CLIParserFree(ctx);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t plaintext[SAM_SC_MAX_FRAME];
|
|
int plaintext_len = 0;
|
|
if (CLIParamHexToBuf(arg_get_str(ctx, 1), plaintext, sizeof(plaintext), &plaintext_len) != PM3_SUCCESS) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
if (plaintext_len < 1) {
|
|
PrintAndLogEx(FAILED, "--payload must be at least 1 byte");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
CLIParserFree(ctx);
|
|
|
|
PrintAndLogEx(INFO, "Plaintext : %s", sprint_hex_inrow(plaintext, plaintext_len));
|
|
|
|
uint8_t wrapped[SAM_SC_MAX_FRAME];
|
|
uint16_t wrapped_len = sam_sc_wrap(plaintext, (uint16_t)plaintext_len,
|
|
wrapped, sizeof(wrapped));
|
|
if (wrapped_len == 0) {
|
|
PrintAndLogEx(FAILED, "wrap() failed");
|
|
return PM3_ESOFT;
|
|
}
|
|
if (verbose)
|
|
PrintAndLogEx(DEBUG, "wrapped (%u): %s", wrapped_len, sprint_hex_inrow(wrapped, wrapped_len));
|
|
|
|
uint8_t sam_payload[SAM_SC_MAX_FRAME + 4];
|
|
uint16_t off = 0;
|
|
sam_payload[off++] = 0xA0;
|
|
off += sam_sc_emit_ber_len(sam_payload + off, wrapped_len);
|
|
if ((uint32_t)off + wrapped_len > sizeof(sam_payload)) {
|
|
PrintAndLogEx(FAILED, "wrapped payload too large for buffer");
|
|
return PM3_ESOFT;
|
|
}
|
|
memcpy(sam_payload + off, wrapped, wrapped_len);
|
|
off += wrapped_len;
|
|
|
|
uint8_t resp[PM3_CMD_DATA_SIZE];
|
|
uint16_t resp_len = sizeof(resp);
|
|
uint8_t sam_flag = 0;
|
|
int rc = sam_sc_dispatch(0, s_sam_sc.sc_flag,
|
|
sam_payload, off, &sam_flag, resp, &resp_len);
|
|
if (rc != PM3_SUCCESS) {
|
|
PrintAndLogEx(FAILED, "dispatch failed (rc=%d)", rc);
|
|
return rc;
|
|
}
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "scFlag=0x%02X resp(%u): %s",
|
|
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
|
}
|
|
if (sam_flag != s_sam_sc.sc_flag && verbose) {
|
|
PrintAndLogEx(WARNING, "SAM scFlag changed: was 0x%02X, now 0x%02X",
|
|
s_sam_sc.sc_flag, sam_flag);
|
|
}
|
|
|
|
const uint8_t *inner = NULL;
|
|
uint16_t inner_len = 0;
|
|
char path = '?';
|
|
if (sam_sc_peel_envelope(resp, resp_len, &inner, &inner_len, &path) != 0) {
|
|
PrintAndLogEx(FAILED, "could not peel SAM response envelope");
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
|
return PM3_ESOFT;
|
|
}
|
|
if (verbose)
|
|
PrintAndLogEx(DEBUG, "envelope=Path %c, inner ciphertext+MAC (%u): %s",
|
|
path, inner_len, sprint_hex_inrow(inner, inner_len));
|
|
|
|
uint8_t plain_resp[SAM_SC_MAX_FRAME];
|
|
int plain_len = sam_sc_unwrap(inner, inner_len, plain_resp, sizeof(plain_resp));
|
|
if (plain_len < 0) {
|
|
PrintAndLogEx(FAILED, _RED_("unwrap failed") " - MAC mismatch or padding error");
|
|
if (verbose) PrintAndLogEx(DEBUG, "raw response: %s", sprint_hex_inrow(resp, resp_len));
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (path == 'C') {
|
|
PrintAndLogEx(WARNING, _YELLOW_("Path C errorResponse") " - SAM returned an error");
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Decrypt OK") " (Path %c)", path);
|
|
}
|
|
PrintAndLogEx(INFO, "Plaintext response (%d): %s",
|
|
plain_len, sprint_hex_inrow(plain_resp, plain_len));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf iclass sam scclose
|
|
// ---------------------------------------------------------------------------
|
|
static int CmdHFiClassSAMSCClose(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass sam scclose",
|
|
"Send Terminate (91 00) over the open SC and release the\n"
|
|
"firmware-side SAM session.",
|
|
"hf iclass sam scclose");
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
CLIParserFree(ctx);
|
|
|
|
if (s_sam_sc.open == false) {
|
|
PrintAndLogEx(WARNING, "no open secure channel - nothing to close");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
uint8_t terminate[2] = {0x91, 0x00};
|
|
uint8_t wrapped[64];
|
|
uint16_t wrapped_len = sam_sc_wrap(terminate, sizeof(terminate),
|
|
wrapped, sizeof(wrapped));
|
|
if (wrapped_len == 0) {
|
|
PrintAndLogEx(FAILED, "wrap() of Terminate failed - forcing local close");
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
uint8_t sam_payload[80];
|
|
uint16_t off = 0;
|
|
sam_payload[off++] = 0xA0;
|
|
off += sam_sc_emit_ber_len(sam_payload + off, wrapped_len);
|
|
memcpy(sam_payload + off, wrapped, wrapped_len);
|
|
off += wrapped_len;
|
|
|
|
uint8_t resp[PM3_CMD_DATA_SIZE];
|
|
uint16_t resp_len = sizeof(resp);
|
|
uint8_t sam_flag = 0;
|
|
int rc = sam_sc_dispatch(SAM_SC_FLAG_RELEASE, s_sam_sc.sc_flag,
|
|
sam_payload, off, &sam_flag, resp, &resp_len);
|
|
if (verbose) {
|
|
PrintAndLogEx(DEBUG, "Terminate scFlag=0x%02X resp(%u): %s",
|
|
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
|
}
|
|
|
|
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
|
|
|
if (rc != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Terminate dispatch returned rc=%d - host state cleared anyway", rc);
|
|
return rc;
|
|
}
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Secure channel closed"));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf iclass sam dispatcher
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Backwards-compat router: when invoked without a subcommand keyword (or
|
|
// with a flag-style argument like `--info` / `-d ...` / `-f ...`), defers
|
|
// to the legacy PACS-extraction implementation (CmdHFiClassSAMExtract).
|
|
// When the first word matches a known SC subcommand, dispatches to the
|
|
// scopen / scsend / scclose / help handlers.
|
|
//
|
|
// So all of these continue to work:
|
|
// hf iclass sam (legacy default)
|
|
// hf iclass sam --info (legacy SAM info)
|
|
// hf iclass sam -p -d <hex> (legacy with flags)
|
|
// hf iclass sam -f hf-iclass-dump.bin (legacy emulate-from-file)
|
|
// And these are new:
|
|
// hf iclass sam scopen
|
|
// hf iclass sam scsend --payload <hex>
|
|
// hf iclass sam scclose
|
|
// hf iclass sam help (lists the SC subcommands)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int CmdHFiClassSAMHelp(const char *Cmd);
|
|
|
|
// SC subcommand dispatch table. Note: no `help` entry here on purpose -
|
|
// use `hf iclass sam --help` (or `-h`) for the SAM-level help. The bare-word
|
|
// `help` keyword is still accepted as a deprecated alias (see sam_cmd_is_help_request).
|
|
static command_t SAMSubCommandTable[] = {
|
|
{"scopen", CmdHFiClassSAMSCOpen, IfPm3Smartcard, "Open Artemis SC (InitAuth + ContinueAuth); persists session"},
|
|
{"scsend", CmdHFiClassSAMSCSend, IfPm3Smartcard, "Send wrapped SAMCommand body through the open SC"},
|
|
{"scclose", CmdHFiClassSAMSCClose, IfPm3Smartcard, "Send Terminate; release the SC session"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
static int CmdHFiClassSAMHelp(const char *Cmd) {
|
|
(void)Cmd;
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(NORMAL, _YELLOW_("hf iclass sam") " - HID SAM operations");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(NORMAL, "Secure-channel subcommands:");
|
|
CmdsHelp(SAMSubCommandTable);
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(NORMAL, "Legacy PACS-extraction flags (no subcommand, applied to " _YELLOW_("hf iclass sam") " directly):");
|
|
PrintAndLogEx(NORMAL, " --info get SAM version + serial number (also warms up the SAM)");
|
|
PrintAndLogEx(NORMAL, " -d, --data <hex> DER-encoded SAMCommand to send (raw, no SC)");
|
|
PrintAndLogEx(NORMAL, " -s, --snmp --data is in SNMP format without the A0/94 headers");
|
|
PrintAndLogEx(NORMAL, " -p, --prevent fake the e-purse update during PACS extraction");
|
|
PrintAndLogEx(NORMAL, " --break stop tag interaction at nr-mac (for SIO extract)");
|
|
PrintAndLogEx(NORMAL, " -f, --file <fn> emulate from a dump file instead of a real card");
|
|
PrintAndLogEx(NORMAL, " -n, --nodetect skip card detect + SetDetectedCardInfo");
|
|
PrintAndLogEx(NORMAL, " -k, --keep keep the field active after the command");
|
|
PrintAndLogEx(NORMAL, " -t, --tlv decode the response as TLV");
|
|
PrintAndLogEx(NORMAL, " --shallow shallow modulation");
|
|
PrintAndLogEx(NORMAL, " -v, --verbose verbose output");
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(NORMAL, "Examples:");
|
|
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam") " extract PACS via SAM (defaults)");
|
|
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam --info") " get SAM version + serial (warmup ping)");
|
|
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scopen") " open Artemis secure channel");
|
|
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scsend --payload 8200") " send wrapped SAMCommand");
|
|
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scclose") " terminate the SC session");
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// Returns true if Cmd consists solely of "-h", "--help", or the bare-word
|
|
// "help" (with optional surrounding whitespace). Used to intercept help
|
|
// requests before the dispatcher would otherwise forward them to the legacy
|
|
// CLIParser. The bare-word "help" is kept as a deprecated alias - it's no
|
|
// longer advertised in the subcommand table, but existing scripts/muscle
|
|
// memory still resolve correctly.
|
|
static bool sam_cmd_is_help_request(const char *Cmd) {
|
|
while (*Cmd == ' ' || *Cmd == '\t') Cmd++;
|
|
size_t len = 0;
|
|
while (Cmd[len] && Cmd[len] != ' ' && Cmd[len] != '\t') len++;
|
|
if (len == 0) return false;
|
|
// ensure standalone (no trailing args)
|
|
const char *tail = Cmd + len;
|
|
while (*tail == ' ' || *tail == '\t') tail++;
|
|
if (*tail != '\0') return false;
|
|
if (len == 2 && memcmp(Cmd, "-h", 2) == 0) return true;
|
|
if (len == 6 && memcmp(Cmd, "--help", 6) == 0) return true;
|
|
if (len == 4 && memcmp(Cmd, "help", 4) == 0) return true;
|
|
return false;
|
|
}
|
|
|
|
static int CmdHFiClassSAM(const char *Cmd) {
|
|
// Skip leading whitespace.
|
|
while (*Cmd == ' ' || *Cmd == '\t') Cmd++;
|
|
|
|
// `--help` / `-h` (standalone) -> SC-aware help instead of the legacy
|
|
// CLIParser's auto-help (which would hide the SC subcommands). The
|
|
// bare-word `help` is also accepted as a deprecated alias (no longer
|
|
// listed in the subcommand table but still resolves here).
|
|
if (sam_cmd_is_help_request(Cmd)) {
|
|
return CmdHFiClassSAMHelp(Cmd);
|
|
}
|
|
|
|
// No args or a flag-style invocation -> legacy PACS-extract implementation.
|
|
// Preserves `hf iclass sam`, `hf iclass sam --info`, `hf iclass sam -d ...`,
|
|
// `hf iclass sam -f ...` etc.
|
|
if (*Cmd == '\0' || *Cmd == '-') {
|
|
return CmdHFiClassSAMExtract(Cmd);
|
|
}
|
|
|
|
// Match the first word against known SC subcommands. The CmdsParse
|
|
// dispatcher would print help on no match, but we want unknown first
|
|
// words to fall through to the legacy implementation, so we check first.
|
|
size_t word_len = 0;
|
|
while (Cmd[word_len] && Cmd[word_len] != ' ' && Cmd[word_len] != '\t')
|
|
word_len++;
|
|
|
|
for (size_t i = 0; SAMSubCommandTable[i].Name != NULL; i++) {
|
|
const char *name = SAMSubCommandTable[i].Name;
|
|
size_t nl = strlen(name);
|
|
if (nl == word_len && memcmp(name, Cmd, word_len) == 0) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(SAMSubCommandTable, Cmd);
|
|
}
|
|
}
|
|
|
|
// Unknown first word - defer to legacy (which will either parse it as a
|
|
// free-form arg or error out cleanly with its own help).
|
|
return CmdHFiClassSAMExtract(Cmd);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// hf iclass liberate — detect and liberate MKF / iCopy-X cloned cards
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// iCopy-X DRM keys
|
|
static const uint8_t icopy_key_icl[PICOPASS_BLOCK_SIZE] = { 0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88 };
|
|
static const uint8_t icopy_key_ics[PICOPASS_BLOCK_SIZE] = { 0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88 };
|
|
|
|
// MKF 3DES key suffix (appended to CSN to form 16-byte 2-key 3DES key: CSN || suffix)
|
|
static const uint8_t mkf_key_suffix[8] = { 0x05, 0x70, 0xF6, 0x9A, 0x06, 0x97, 0x5C, 0xD8 };
|
|
|
|
// MKF expected plaintext for block 18
|
|
static const uint8_t mkf_expected_pt[PICOPASS_BLOCK_SIZE] = { 0xCD, 0x00, 0x00, 0x00, 0xCD, 0xFF, 0xFF, 0xFF };
|
|
|
|
#define MKF_KNOWN_BLOCK ( 18 )
|
|
|
|
typedef enum {
|
|
CARD_TYPE_UNKNOWN = 0,
|
|
CARD_TYPE_MKF,
|
|
CARD_TYPE_ICOPY_ICL,
|
|
CARD_TYPE_ICOPY_ICS,
|
|
} liberate_card_type_t;
|
|
|
|
static int iclass_mfk_selftest(void) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--------------- " _CYAN_("selftest") " -----------------------");
|
|
|
|
// Test vector: CSN E44B4403F8FF12E0, encrypted block 18 = B34E6C637CEDFE9C
|
|
// 3DES key = CSN || 0570F69A06975CD8 = E44B4403F8FF12E00570F69A06975CD8
|
|
// Expected plaintext = CD000000CDFFFFFF
|
|
const uint8_t tv_csn[8] = { 0xE4, 0x4B, 0x44, 0x03, 0xF8, 0xFF, 0x12, 0xE0 };
|
|
const uint8_t tv_blk18[8] = { 0xB3, 0x4E, 0x6C, 0x63, 0x7C, 0xED, 0xFE, 0x9C };
|
|
|
|
// build 3DES key
|
|
uint8_t des_key[16] = {0};
|
|
memcpy(des_key, tv_csn, 8);
|
|
memcpy(des_key + 8, mkf_key_suffix, 8);
|
|
|
|
// decrypt
|
|
uint8_t decrypted[8] = {0};
|
|
mbedtls_des3_context des3_ctx;
|
|
mbedtls_des3_set2key_dec(&des3_ctx, des_key);
|
|
mbedtls_des3_crypt_ecb(&des3_ctx, tv_blk18, decrypted);
|
|
mbedtls_des3_free(&des3_ctx);
|
|
|
|
PrintAndLogEx(INFO, "CSN............ %s", sprint_hex_inrow(tv_csn, 8));
|
|
PrintAndLogEx(INFO, "Block 18 enc... %s", sprint_hex_inrow(tv_blk18, 8));
|
|
PrintAndLogEx(INFO, "2k3DES key..... %s", sprint_hex_inrow(des_key, 16));
|
|
PrintAndLogEx(INFO, "Decrypted...... %s", sprint_hex_inrow(decrypted, 8));
|
|
|
|
if (memcmp(decrypted, mkf_expected_pt, 8) == 0) {
|
|
PrintAndLogEx(SUCCESS, "MKF test ( %s )", _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(FAILED, "MKF test ( %s )", _RED_("fail"));
|
|
return PM3_ESOFT;
|
|
}
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFiClassLiberate(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass liberate",
|
|
"Detect and liberate MKF or iCopy-X cloned iCLASS cards.\n"
|
|
"MKF cards: verifies block 18 signature, then zeroes it.\n"
|
|
"iCopy-X cards: detects DRM key, then changes KD to default (ki 0).\n",
|
|
"hf iclass liberate\n"
|
|
"hf iclass liberate --selftest\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "selftest", "run MKF detection self-test"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool shallow_mod = arg_get_lit(ctx, 1);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool selftest = arg_get_lit(ctx, 3);
|
|
CLIParserFree(ctx);
|
|
|
|
if (selftest) {
|
|
return iclass_mfk_selftest();
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// Select card, get CSN
|
|
uint8_t csn[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t CCNR[12] = {0};
|
|
|
|
if (select_only(csn, CCNR, true, shallow_mod) == false) {
|
|
DropField();
|
|
PrintAndLogEx(ERR, "Failed to select card");
|
|
return PM3_ESOFT;
|
|
}
|
|
DropField();
|
|
|
|
PrintAndLogEx(SUCCESS, "CSN: " _GREEN_("%s"), sprint_hex_inrow(csn, PICOPASS_BLOCK_SIZE));
|
|
|
|
liberate_card_type_t card_type = CARD_TYPE_UNKNOWN;
|
|
uint8_t drm_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
|
|
// Try MKF detection — read block 18 with default key (ki 0)
|
|
PrintAndLogEx(INFO, "Checking for MKF signature...");
|
|
{
|
|
uint8_t blk18[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t key[PICOPASS_BLOCK_SIZE];
|
|
memcpy(key, iClass_Key_Table[0], PICOPASS_BLOCK_SIZE);
|
|
|
|
int res = iclass_read_block_ex(key, MKF_KNOWN_BLOCK, ICLASS_DEBIT_KEYTYPE, false, false, false,
|
|
verbose, true, shallow_mod, blk18, false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
// build 2-key 3DES key: CSN || 0570F69A06975CD8
|
|
uint8_t des_key[16] = {0};
|
|
memcpy(des_key, csn, 8);
|
|
memcpy(des_key + 8, mkf_key_suffix, 8);
|
|
|
|
// decrypt block 18
|
|
uint8_t decrypted[8] = {0};
|
|
mbedtls_des3_context des3_ctx;
|
|
mbedtls_des3_set2key_dec(&des3_ctx, des_key);
|
|
mbedtls_des3_crypt_ecb(&des3_ctx, blk18, decrypted);
|
|
mbedtls_des3_free(&des3_ctx);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Block 18..... %s", sprint_hex_inrow(blk18, 8));
|
|
PrintAndLogEx(INFO, "2k3DES key... %s", sprint_hex_inrow(des_key, 16));
|
|
PrintAndLogEx(INFO, "Decrypted.... %s", sprint_hex_inrow(decrypted, 8));
|
|
}
|
|
|
|
if (memcmp(decrypted, mkf_expected_pt, 8) == 0) {
|
|
PrintAndLogEx(SUCCESS, "Detected " _GREEN_("MKF card") ", block %u signature verified", MKF_KNOWN_BLOCK);
|
|
card_type = CARD_TYPE_MKF;
|
|
} else {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Not MFK. Block %u decrypted to %s", MKF_KNOWN_BLOCK, sprint_hex_inrow(decrypted, 8));
|
|
}
|
|
}
|
|
} else {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "Block %u read with ki 0 ( %s )", MKF_KNOWN_BLOCK, _RED_("fail"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try iCopy-X detection — attempt auth with each DRM key
|
|
if (card_type == CARD_TYPE_UNKNOWN) {
|
|
PrintAndLogEx(INFO, "Checking for iCopy-X DRM keys...");
|
|
|
|
// try iCL key first
|
|
uint8_t dummy[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t key_icl[PICOPASS_BLOCK_SIZE];
|
|
memcpy(key_icl, icopy_key_icl, PICOPASS_BLOCK_SIZE);
|
|
|
|
int res = iclass_read_block_ex(key_icl, 6, ICLASS_DEBIT_KEYTYPE, false, false, false, verbose, true, shallow_mod, dummy, false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Detected " _GREEN_("iCopy-X iCL") ", DRM key 2020666666668888");
|
|
card_type = CARD_TYPE_ICOPY_ICL;
|
|
memcpy(drm_key, icopy_key_icl, PICOPASS_BLOCK_SIZE);
|
|
} else {
|
|
// try iCS key
|
|
uint8_t key_ics[PICOPASS_BLOCK_SIZE];
|
|
memcpy(key_ics, icopy_key_ics, PICOPASS_BLOCK_SIZE);
|
|
|
|
res = iclass_read_block_ex(key_ics, 6, ICLASS_DEBIT_KEYTYPE, false, false, false, verbose, true, shallow_mod, dummy, false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Detected " _GREEN_("iCopy-X iCS") ", DRM key 6666202066668888");
|
|
card_type = CARD_TYPE_ICOPY_ICS;
|
|
memcpy(drm_key, icopy_key_ics, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
|
|
// Perform liberation
|
|
switch (card_type) {
|
|
|
|
case CARD_TYPE_MKF: {
|
|
// write all-zero block 18
|
|
PrintAndLogEx(INFO, "Zeroing block %u...", MKF_KNOWN_BLOCK);
|
|
|
|
uint8_t key[PICOPASS_BLOCK_SIZE];
|
|
memcpy(key, iClass_Key_Table[0], PICOPASS_BLOCK_SIZE);
|
|
|
|
int res = iclass_write_block(MKF_KNOWN_BLOCK, zeros, NULL, key, false, false, false, false, verbose, false, shallow_mod);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "MFK block %u write ( %s )", MKF_KNOWN_BLOCK, _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Write block %u ( %s )", MKF_KNOWN_BLOCK, _RED_("fail"));
|
|
return res;
|
|
}
|
|
|
|
// verify
|
|
uint8_t verify[PICOPASS_BLOCK_SIZE] = {0};
|
|
res = iclass_read_block_ex(key, MKF_KNOWN_BLOCK, ICLASS_DEBIT_KEYTYPE, false, false, false, verbose, true, shallow_mod, verify, false, false);
|
|
if (res != PM3_SUCCESS) {
|
|
PrintAndLogEx(WARNING, "Reading block %u ( %s )", MKF_KNOWN_BLOCK, _RED_("fail"));
|
|
return res;
|
|
}
|
|
|
|
if (memcmp(verify, zeros, PICOPASS_BLOCK_SIZE) == 0) {
|
|
PrintAndLogEx(SUCCESS, "Block %u cleared ( %s )", MKF_KNOWN_BLOCK, _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Block %u not cleared ( %s )", MKF_KNOWN_BLOCK, _RED_("fail"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CARD_TYPE_ICOPY_ICL:
|
|
case CARD_TYPE_ICOPY_ICS: {
|
|
// change KD from DRM key to default key (ki 0)
|
|
PrintAndLogEx(INFO, "Changing KD from iCopy-X DRM key to default");
|
|
|
|
// calculate XOR div key
|
|
uint8_t xor_div_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
HFiClassCalcNewKey(csn, drm_key, iClass_Key_Table[0], xor_div_key, false, false, verbose);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, "XOR div key... %s", sprint_hex_inrow(xor_div_key, PICOPASS_BLOCK_SIZE));
|
|
}
|
|
|
|
// write XOR'd key to block 3 using current DRM key for auth
|
|
uint8_t auth_key[PICOPASS_BLOCK_SIZE];
|
|
memcpy(auth_key, drm_key, PICOPASS_BLOCK_SIZE);
|
|
|
|
int res = iclass_write_block(3, xor_div_key, NULL, auth_key, false, false, false, false, verbose, false, shallow_mod);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Change to default key ( %s )", _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Change to default key ( %s )", _RED_("fail"));
|
|
return res;
|
|
}
|
|
|
|
// verify — try reading block 6 with default key
|
|
uint8_t verify[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t default_key[PICOPASS_BLOCK_SIZE];
|
|
memcpy(default_key, iClass_Key_Table[0], PICOPASS_BLOCK_SIZE);
|
|
|
|
res = iclass_read_block_ex(default_key, 6, ICLASS_DEBIT_KEYTYPE, false, false, false,
|
|
verbose, true, shallow_mod, verify, false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, "Verified default key ( %s )", _GREEN_("ok"));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Verified default key ( %s )", _RED_("fail"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CARD_TYPE_UNKNOWN: {
|
|
PrintAndLogEx(INFO, "Card is neither MKF nor iCopy-X (or authentication failed)");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static command_t CommandTable[] = {
|
|
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
|
{"list", CmdHFiClassList, AlwaysAvailable, "List iclass history"},
|
|
// {"-----------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
|
|
{"-----------", CmdHelp, IfPm3Iclass, "------------------- " _CYAN_("Operations") " -------------------"},
|
|
// {"clone", CmdHFiClassClone, IfPm3Iclass, "Create a HID credential to Picopass / iCLASS tag"},
|
|
{"dump", CmdHFiClassDump, IfPm3Iclass, "Dump Picopass / iCLASS tag to file"},
|
|
{"info", CmdHFiClassInfo, IfPm3Iclass, "Tag information"},
|
|
{"rdbl", CmdHFiClass_ReadBlock, IfPm3Iclass, "Read Picopass / iCLASS block"},
|
|
{"reader", CmdHFiClassReader, IfPm3Iclass, "Act like a Picopass / iCLASS reader"},
|
|
{"restore", CmdHFiClassRestore, IfPm3Iclass, "Restore a dump file onto a Picopass / iCLASS tag"},
|
|
{"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"},
|
|
{"tear", CmdHFiClass_TearBlock, IfPm3Iclass, "Performs tearoff attack on iCLASS block"},
|
|
{"liberate", CmdHFiClassLiberate, IfPm3Iclass, "Detect and liberate MKF / iCopy-X cloned cards"},
|
|
{"-----------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("Recovery") " --------------------"},
|
|
// {"autopwn", CmdHFiClassAutopwn, IfPm3Iclass, "Automatic key recovery tool for iCLASS"},
|
|
{"chk", CmdHFiClassCheckKeys, IfPm3Iclass, "Check keys"},
|
|
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
|
|
{"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"},
|
|
{"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"},
|
|
{"blacktears", CmdHFiClass_BlackTears, IfPm3Iclass, "Automated tearoff attack on new silicon cards to enable non-secure page mode"},
|
|
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
|
|
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
|
|
{"tagsim", CmdHFiClassTagSim, IfPm3Iclass, "Simulate a full iCLASS 2K tag from FC/CN and keys"},
|
|
{"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"},
|
|
{"esave", CmdHFiClassESave, IfPm3Iclass, "Save emulator memory to file"},
|
|
{"esetblk", CmdHFiClassESetBlk, IfPm3Iclass, "Set emulator memory block data"},
|
|
{"eview", CmdHFiClassEView, IfPm3Iclass, "View emulator memory"},
|
|
{"-----------", CmdHelp, AlwaysAvailable, "---------------------- " _CYAN_("Utils") " ----------------------"},
|
|
{"configcard", CmdHFiClassConfigCard, IfPm3Iclass, "Reader configuration card generator"},
|
|
{"calcnewkey", CmdHFiClassCalcNewKey, AlwaysAvailable, "Calc diversified keys (blocks 3 & 4) to write new keys"},
|
|
{"encode", CmdHFiClassEncode, AlwaysAvailable, "Encode binary wiegand to block 7"},
|
|
{"encrypt", CmdHFiClassEncryptBlk, AlwaysAvailable, "Encrypt given block data"},
|
|
{"decrypt", CmdHFiClassDecrypt, AlwaysAvailable, "Decrypt given block data or tag dump file" },
|
|
{"managekeys", CmdHFiClassManageKeys, AlwaysAvailable, "Manage keys to use with iclass commands"},
|
|
{"permutekey", CmdHFiClassPermuteKey, AlwaysAvailable, "Permute function from 'heart of darkness' paper"},
|
|
{"-----------", CmdHelp, IfPm3Smartcard, "----------------------- " _CYAN_("SAM") " -----------------------"},
|
|
{"sam", CmdHFiClassSAM, IfPm3Smartcard, "SAM ops: PACS extract + secure channel (scopen/scsend/scclose)"},
|
|
{NULL, NULL, NULL, NULL}
|
|
};
|
|
|
|
static int CmdHelp(const char *Cmd) {
|
|
(void)Cmd; // Cmd is not used so far
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
int CmdHFiClass(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|
|
|
|
// static void test_credential_type(void) {
|
|
// need AA1 key
|
|
// Block 5 -> tells if its a legacy or SIO, also tells which key to use.
|
|
|
|
// tech | blocks used | desc | num of payloads
|
|
// -------+-----------------------+-----------------------------------+------
|
|
// legacy | 6,7,8,9 | AA!, Access control payload | 1
|
|
// SE | 6,7,8,9,10,11,12 | AA1, Secure identity object (SIO) | 1
|
|
// SR | 6,7,8,9, | AA1, Access control payload | 2
|
|
// | 10,11,12,13,14,15,16 | AA1, Secure identity object (SIO) |
|
|
// SEOS | | |
|
|
// MFC SIO| | |
|
|
// DESFIRE| | |
|
|
//}
|
|
|
|
int info_iclass(bool shallow_mod) {
|
|
|
|
iclass_card_select_t payload = {
|
|
.flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE),
|
|
.page = 0 // no page selection support for info yet
|
|
};
|
|
|
|
if (shallow_mod) {
|
|
payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
PacketResponseNG resp;
|
|
SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload, sizeof(iclass_card_select_t));
|
|
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
|
|
DropField();
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
DropField();
|
|
|
|
iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
|
|
|
|
uint8_t *p_response = (uint8_t *)&r->header.hdr;
|
|
// no tag found or button pressed
|
|
if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
|
|
return PM3_EOPABORTED;
|
|
}
|
|
|
|
picopass_hdr_t *hdr = &r->header.hdr;
|
|
picopass_ns_hdr_t *ns_hdr = &r->header.ns_hdr;
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " -------------------------------------");
|
|
|
|
if ((r->status & FLAG_ICLASS_CSN) == FLAG_ICLASS_CSN) {
|
|
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s") " uid", sprint_hex(hdr->csn, sizeof(hdr->csn)));
|
|
}
|
|
|
|
if ((r->status & FLAG_ICLASS_CONF) == FLAG_ICLASS_CONF) {
|
|
PrintAndLogEx(SUCCESS, " Config: %s card configuration", sprint_hex((uint8_t *)&hdr->conf, sizeof(hdr->conf)));
|
|
}
|
|
|
|
// page mapping. If fuse0|1 == 0x01, card is in non-secure mode, with CSN, CONF, AIA as top 3 blocks.
|
|
// page9 in http://www.proxmark.org/files/Documents/13.56%20MHz%20-%20iClass/DS%20Picopass%202KS%20V1-0.pdf
|
|
uint8_t pagemap = get_pagemap(hdr);
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(SUCCESS, " AIA: %s application issuer area", sprint_hex(ns_hdr->app_issuer_area, sizeof(ns_hdr->app_issuer_area)));
|
|
} else {
|
|
|
|
if ((r->status & FLAG_ICLASS_CC) == FLAG_ICLASS_CC) {
|
|
PrintAndLogEx(SUCCESS, "E-purse: %s card challenge, CC", sprint_hex(hdr->epurse, sizeof(hdr->epurse)));
|
|
}
|
|
|
|
if (memcmp(hdr->key_d, zeros, sizeof(zeros))) {
|
|
PrintAndLogEx(SUCCESS, " Kd: " _YELLOW_("%s") " debit key", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Kd: -- -- -- -- -- -- -- -- debit key ( hidden )");
|
|
}
|
|
|
|
if (memcmp(hdr->key_c, zeros, sizeof(zeros))) {
|
|
PrintAndLogEx(SUCCESS, " Kc: " _YELLOW_("%s") " credit key", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Kc: -- -- -- -- -- -- -- -- credit key ( hidden )");
|
|
}
|
|
|
|
|
|
if ((r->status & FLAG_ICLASS_AIA) == FLAG_ICLASS_AIA) {
|
|
PrintAndLogEx(SUCCESS, " AIA: %s application issuer area", sprint_hex(hdr->app_issuer_area, sizeof(hdr->app_issuer_area)));
|
|
}
|
|
}
|
|
|
|
if ((r->status & FLAG_ICLASS_CONF) == FLAG_ICLASS_CONF) {
|
|
print_picopass_info(hdr);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "----------------------- " _CYAN_("Fingerprint") " ---------------------");
|
|
|
|
uint8_t aia[8];
|
|
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
memcpy(aia, ns_hdr->app_issuer_area, sizeof(aia));
|
|
} else {
|
|
memcpy(aia, hdr->app_issuer_area, sizeof(aia));
|
|
}
|
|
|
|
// if CSN starts with E012FFF (big endian), it's inside HID CSN range.
|
|
bool is_hid_range = (hdr->csn[4] & 0xF0) == 0xF0 && (memcmp(hdr->csn + 5, "\xFF\x12\xE0", 3) == 0);
|
|
|
|
bool legacy = (memcmp(aia, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
|
|
bool se_enabled = (memcmp(aia, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
|
|
|
|
if (is_hid_range) {
|
|
PrintAndLogEx(SUCCESS, " CSN.......... " _YELLOW_("HID range"));
|
|
|
|
if (legacy) {
|
|
PrintAndLogEx(SUCCESS, " Credential... " _GREEN_("iCLASS legacy"));
|
|
}
|
|
|
|
if (se_enabled) {
|
|
PrintAndLogEx(SUCCESS, " Credential... " _GREEN_("iCLASS SE"));
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " CSN.......... " _YELLOW_("outside HID range"));
|
|
}
|
|
|
|
uint8_t cardtype = get_mem_config(hdr);
|
|
PrintAndLogEx(SUCCESS, " Card type.... " _GREEN_("%s"), card_types[cardtype]);
|
|
|
|
if (memcmp(hdr->csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) {
|
|
PrintAndLogEx(SUCCESS, " Card chip.... " _YELLOW_("New silicon (No 14b support)"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Card chip.... " _YELLOW_("Old silicon (14b support)"));
|
|
}
|
|
if (legacy) {
|
|
|
|
int res = PM3_ESOFT;
|
|
uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0};
|
|
// we take all raw bytes from response
|
|
memcpy(dump, p_response, sizeof(picopass_hdr_t));
|
|
|
|
bool found_aa1 = false;
|
|
bool found_aa2 = false;
|
|
uint8_t aa1_idx = 0;
|
|
uint8_t key[PICOPASS_KEY_SIZE] = {0};
|
|
|
|
for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) {
|
|
|
|
memcpy(key, iClass_Key_Table[i], sizeof(key));
|
|
|
|
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, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
found_aa1 = true;
|
|
aa1_idx = i;
|
|
}
|
|
}
|
|
|
|
res = iclass_read_block_ex(key, 6, ICLASS_CREDIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, " AA2 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
found_aa2 = true;
|
|
}
|
|
|
|
if (found_aa1 && found_aa2) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found_aa1) {
|
|
|
|
res = iclass_read_block_ex(iClass_Key_Table[aa1_idx], 7, ICLASS_DEBIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false, false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(INFO, "");
|
|
|
|
BLOCK79ENCRYPTION aa1_encryption = (dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);
|
|
|
|
uint8_t decrypted[PICOPASS_BLOCK_SIZE * 8] = {0};
|
|
memcpy(decrypted, dump, 7 * PICOPASS_BLOCK_SIZE);
|
|
|
|
uint8_t transport[16] = {0};
|
|
iclass_load_transport(transport, sizeof(transport));
|
|
iclass_decrypt_transport(transport, 8, dump, decrypted, aa1_encryption);
|
|
iclass_decode_credentials(decrypted);
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|