mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-11 23:24:43 +00:00
45ae30fe88
Removed a huge chunk of colorful visual spam for when the tearoff isn't happening
5928 lines
214 KiB
C
5928 lines
214 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>
|
|
#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/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 "preferences.h"
|
|
#include "generator.h"
|
|
#include "cmdhf14b.h"
|
|
#include "cmdhw.h"
|
|
#include "hidsio.h"
|
|
|
|
|
|
#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) {
|
|
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));
|
|
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[33] = {
|
|
//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}}
|
|
};
|
|
|
|
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)];
|
|
}
|
|
|
|
static void print_config_cards(void) {
|
|
PrintAndLogEx(INFO, "---- " _CYAN_("Config cards options") " ------------");
|
|
for (int i = 0; i < ARRAYLEN(iclass_config_options) ; ++i) {
|
|
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);
|
|
}
|
|
|
|
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... %s debit key ( hidden )", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
|
|
}
|
|
|
|
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... %s credit key ( hidden )", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
|
|
}
|
|
|
|
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");
|
|
|
|
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) {
|
|
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:
|
|
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)
|
|
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf iclass esave -h") "` to save the emulator memory to file");
|
|
break;
|
|
}
|
|
}
|
|
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)
|
|
};
|
|
|
|
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) {
|
|
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;
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Failed to allocate memory");
|
|
res = PM3_EMALLOC;
|
|
}
|
|
}
|
|
} 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) {
|
|
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;
|
|
}
|
|
memset(dump, 0, bytes);
|
|
|
|
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) {
|
|
|
|
// todo: remove preamble/sentinel
|
|
PrintAndLogEx(INFO, "------------------------ " _CYAN_("Block 7 decoder") " --------------------------");
|
|
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;
|
|
while (strlen(pbin) && *(++pbin) == '0');
|
|
|
|
PrintAndLogEx(SUCCESS, "Binary... " _GREEN_("%s") " ( %zu )", pbin, strlen(pbin));
|
|
PrintAndLogEx(NORMAL, "");
|
|
decode_wiegand(top, mid, bot, 0);
|
|
}
|
|
|
|
} else {
|
|
PrintAndLogEx(INFO, "No unencrypted legacy credential found");
|
|
}
|
|
}
|
|
|
|
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 == false) {
|
|
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)
|
|
};
|
|
|
|
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, 8);
|
|
|
|
if (CCNR != NULL)
|
|
memcpy(CCNR, hdr->epurse, 8);
|
|
|
|
if (verbose) {
|
|
PrintAndLogEx(SUCCESS, "CSN %s", sprint_hex(CSN, 8));
|
|
PrintAndLogEx(SUCCESS, "epurse %s", sprint_hex(CCNR, 8));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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_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};
|
|
bool auth = false;
|
|
|
|
CLIGetHexWithReturn(ctx, 2, key, &key_len);
|
|
|
|
int deb_key_nr = arg_get_int_def(ctx, 3, -1);
|
|
|
|
if (key_len > 0 && deb_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify debit key or index, not both");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (key_len > 0) {
|
|
auth = true;
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Debit key is incorrect length");
|
|
CLIParserFree(ctx);
|
|
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");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
int credit_key_len = 0;
|
|
uint8_t credit_key[8] = {0};
|
|
bool have_credit_key = false;
|
|
|
|
CLIGetHexWithReturn(ctx, 4, credit_key, &credit_key_len);
|
|
|
|
int credit_key_nr = arg_get_int_def(ctx, 5, -1);
|
|
|
|
if (credit_key_len > 0 && credit_key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify credit key or index, not both");
|
|
CLIParserFree(ctx);
|
|
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");
|
|
CLIParserFree(ctx);
|
|
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");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
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)
|
|
};
|
|
|
|
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) {
|
|
PrintAndLogEx(FAILED, "Run command with keys");
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
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,
|
|
};
|
|
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)) {
|
|
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)) {
|
|
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) == 0) {
|
|
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"),
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int 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;
|
|
}
|
|
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 -d FEFFFFFF -k 001122334455667B\n"
|
|
"hf iclass creditepurse -d FEFFFFFF --ki 0");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("k", "key", "<hex>", "Credit key as 8 hex bytes"),
|
|
arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
|
|
arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
|
|
arg_lit0(NULL, "elite", "elite computations applied to key"),
|
|
arg_lit0(NULL, "raw", "no computations applied to key"),
|
|
arg_lit0("v", "verbose", "verbose output"),
|
|
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
int key_len = 0;
|
|
uint8_t key[8] = {0};
|
|
|
|
CLIGetHexWithReturn(ctx, 1, key, &key_len);
|
|
|
|
int key_nr = arg_get_int_def(ctx, 2, -1);
|
|
|
|
if (key_len > 0 && key_nr >= 0) {
|
|
PrintAndLogEx(ERR, "Please specify key or index, not both");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (key_len > 0) {
|
|
if (key_len != 8) {
|
|
PrintAndLogEx(ERR, "Key is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else if (key_nr >= 0) {
|
|
if (key_nr < ICLASS_KEYS_MAX) {
|
|
memcpy(key, iClass_Key_Table[key_nr], 8);
|
|
PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key number is invalid");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
PrintAndLogEx(ERR, "Key or key number must be provided");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int blockno = 2;
|
|
|
|
int data_len = 0;
|
|
uint8_t data[4] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, data, &data_len);
|
|
|
|
if (data_len != 4) {
|
|
PrintAndLogEx(ERR, "Data must be 4 hex bytes (8 hex symbols)");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
bool elite = arg_get_lit(ctx, 4);
|
|
bool rawkey = arg_get_lit(ctx, 5);
|
|
bool verbose = arg_get_lit(ctx, 6);
|
|
bool shallow_mod = arg_get_lit(ctx, 7);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw'");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
iclass_credit_epurse_t payload = {
|
|
.req.use_raw = rawkey,
|
|
.req.use_elite = elite,
|
|
.req.use_credit_key = true,
|
|
.req.use_replay = false,
|
|
.req.blockno = blockno,
|
|
.req.send_reply = true,
|
|
.req.do_auth = true,
|
|
.req.shallow_mod = shallow_mod,
|
|
};
|
|
memcpy(payload.req.key, key, 8);
|
|
memcpy(payload.epurse, data, sizeof(payload.epurse));
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_ICLASS_CREDIT_EPURSE, (uint8_t *)&payload, sizeof(payload));
|
|
PacketResponseNG resp;
|
|
|
|
int isok;
|
|
if (WaitForResponseTimeout(CMD_HF_ICLASS_CREDIT_EPURSE, &resp, 2000) == 0) {
|
|
if (verbose) PrintAndLogEx(WARNING, "command 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_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);
|
|
|
|
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);
|
|
payload->req.use_raw = rawkey;
|
|
payload->req.use_elite = elite;
|
|
payload->req.use_credit_key = use_credit_key;
|
|
payload->req.use_replay = false;
|
|
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) == 0) {
|
|
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) {
|
|
|
|
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) {
|
|
PrintAndLogEx(NORMAL, "");
|
|
PrintAndLogEx(SUCCESS, " block %3d/0x%02X : " _GREEN_("%s"), blockno, blockno, sprint_hex(packet->data, sizeof(packet->data)));
|
|
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) {
|
|
return iclass_read_block_ex(KEY, blockno, keyType, elite, rawkey, replay, verbose, auth, shallow_mod, out, true);
|
|
}
|
|
|
|
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_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 = 0x88; //debit key
|
|
if (arg_get_lit(ctx, 4)) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
|
keyType = 0x18; //credit key
|
|
}
|
|
|
|
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);
|
|
|
|
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`");
|
|
|
|
}
|
|
|
|
uint8_t data[8] = {0};
|
|
int res = iclass_read_block(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, data);
|
|
if (res != PM3_SUCCESS)
|
|
return res;
|
|
|
|
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;
|
|
while (strlen(pbin) && *(++pbin) == '0');
|
|
|
|
PrintAndLogEx(SUCCESS, " bin : %s", pbin);
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "------------------------------ " _CYAN_("Wiegand") " -------------------------------");
|
|
decode_wiegand(top, mid, bot, 0);
|
|
}
|
|
} 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",
|
|
"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_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"),
|
|
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/3us."),
|
|
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_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;
|
|
}
|
|
}
|
|
|
|
int tearoff_start = arg_get_int_def(ctx, 12, 5000);
|
|
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 loop_count = 0;
|
|
|
|
|
|
if (tearoff_end <= tearoff_start) {
|
|
PrintAndLogEx(ERR, "Tearoff end delay must be bigger than the start delay.");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (tearoff_start < 0 || tearoff_end <= 0) {
|
|
PrintAndLogEx(ERR, "Tearoff start/end delays should be bigger than 0.");
|
|
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);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
if ((use_replay + rawkey + elite) > 1) {
|
|
PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
|
|
return PM3_EINVARG;
|
|
}
|
|
int isok = 0;
|
|
bool read_ok = false;
|
|
uint8_t keyType = 0x88; //debit key
|
|
|
|
if (use_credit_key) {
|
|
PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
|
|
keyType = 0x18; //credit key
|
|
}
|
|
|
|
//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)
|
|
};
|
|
|
|
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 (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
|
|
PrintAndLogEx(INFO, "Card in non-secure page mode detected");
|
|
auth = false;
|
|
}
|
|
|
|
//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 reread = false;
|
|
bool erase_phase = false;
|
|
|
|
int res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, data_read_orig, false);
|
|
if (res_orig == PM3_SUCCESS && !reread) {
|
|
if (memcmp(data_read_orig, zeros, 8) == 0) {
|
|
reread = true;
|
|
} else {
|
|
first_read = true;
|
|
reread = false;
|
|
}
|
|
} else if (res_orig == PM3_SUCCESS && reread) {
|
|
first_read = true;
|
|
reread = false;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Starting tear off against block %u / 0x%02x", blockno, blockno);
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
|
|
|
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,
|
|
.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;
|
|
}
|
|
|
|
// write
|
|
// don't check the return value. As a tear-off occurred, the write failed.
|
|
PrintAndLogEx(INFO, "Tear off delay: "_YELLOW_("%d")" / "_YELLOW_("%d")" us", tearoff_start, tearoff_end);
|
|
iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
|
|
|
|
//read the data back
|
|
uint8_t data_read[8] = {0};
|
|
first_read = false;
|
|
reread = false;
|
|
bool decrease = false;
|
|
|
|
while (first_read == false) {
|
|
|
|
if (kbd_enter_pressed()) {
|
|
PrintAndLogEx(WARNING, "\naborted via keyboard.");
|
|
isok = PM3_EOPABORTED;
|
|
goto out;
|
|
}
|
|
|
|
res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, shallow_mod, data_read, false);
|
|
if (res == PM3_SUCCESS && !reread) {
|
|
if (memcmp(data_read, zeros, 8) == 0) {
|
|
reread = true;
|
|
} else {
|
|
first_read = true;
|
|
reread = false;
|
|
}
|
|
} else if (res == PM3_SUCCESS && reread) {
|
|
first_read = true;
|
|
reread = false;
|
|
} else if (res != PM3_SUCCESS) {
|
|
decrease = true;
|
|
}
|
|
}
|
|
|
|
if (decrease && tearoff_start > 0) { // if there was an error reading repeat the tearoff with the same delay
|
|
tearoff_start -= tearoff_increment;
|
|
}
|
|
|
|
bool tear_success = true;
|
|
bool expected_values = true;
|
|
|
|
if (memcmp(data_read, data, 8) != 0) {
|
|
tear_success = false;
|
|
}
|
|
|
|
if ((tear_success == false) && (memcmp(data_read, zeros, 8) != 0) && (memcmp(data_read, data_read_orig, 8) != 0)) { // tearoff succeeded (partially)
|
|
|
|
expected_values = false;
|
|
|
|
if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) {
|
|
erase_phase = true;
|
|
PrintAndLogEx(SUCCESS, _BLUE_("Erase phase hit: ALL ONES"));
|
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
|
} else {
|
|
|
|
if (erase_phase) {
|
|
PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write Phase (post erase)"));
|
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, _CYAN_("Tearing! (unknown phase)"));
|
|
iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: ");
|
|
}
|
|
}
|
|
|
|
if (blockno == 1) {
|
|
if (data_read[0] != data_read_orig[0]) {
|
|
PrintAndLogEx(SUCCESS, "Application limit changed, from %u to %u", data_read_orig[0], data_read[0]);
|
|
isok = PM3_SUCCESS;
|
|
goto out;
|
|
}
|
|
if (data_read[7] != data_read_orig[7]) {
|
|
PrintAndLogEx(SUCCESS, "Fuse changed, from %02x to %02x", data_read_orig[7], data_read[7]);
|
|
isok = PM3_SUCCESS;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
} else { // tearoff did not succeed
|
|
PrintAndLogEx(INFO, "Expected: %s", sprint_hex_inrow(data, sizeof(data)));
|
|
PrintAndLogEx(INFO, "Read: %s", sprint_hex_inrow(data_read, sizeof(data_read)));
|
|
}
|
|
|
|
if (tear_success) { // tearoff succeeded with expected values
|
|
read_ok = true;
|
|
tear_success = true;
|
|
if (expected_values) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("Expected values!"));
|
|
}
|
|
PrintAndLogEx(INFO, "Read: "_GREEN_("%s"), sprint_hex_inrow(data_read, sizeof(data_read)));
|
|
}
|
|
loop_count++;
|
|
if (loop_count == tearoff_loop) {
|
|
tearoff_start += tearoff_increment;
|
|
loop_count = 0;
|
|
}
|
|
PrintAndLogEx(INFO, "--------------------------");
|
|
}
|
|
|
|
out:
|
|
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, "");
|
|
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;
|
|
}
|
|
|
|
// print ASN1 decoded array in TLV view
|
|
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, "");
|
|
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: " _YELLOW_("%s"), filename);
|
|
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[8] = {0};
|
|
uint8_t key_sel[8] = { 0 };
|
|
uint8_t key_sel_p[8] = { 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[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t new_div_key[8] = {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(old_div_key, 8));
|
|
PrintAndLogEx(SUCCESS, "New div key......... %s", sprint_hex(new_div_key, 8));
|
|
PrintAndLogEx(SUCCESS, "Xor div key......... " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8));
|
|
}
|
|
}
|
|
|
|
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};
|
|
|
|
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);
|
|
|
|
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 8byte 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, 8);
|
|
}
|
|
|
|
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 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);
|
|
uint8_t aa2_standard_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
memcpy(aa2_standard_key, iClass_Key_Table[1], PICOPASS_BLOCK_SIZE);
|
|
iclass_recover_req_t *payload = calloc(1, payload_size);
|
|
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->req2.use_raw = false;
|
|
payload->req2.use_elite = false;
|
|
payload->req2.use_credit_key = true;
|
|
payload->req2.use_replay = false;
|
|
payload->req2.send_reply = true;
|
|
payload->req2.do_auth = true;
|
|
payload->req2.shallow_mod = false;
|
|
payload->index = index_start;
|
|
payload->loop = loop;
|
|
payload->debug = debug;
|
|
payload->test = test;
|
|
memcpy(payload->nfa, no_first_auth, PICOPASS_BLOCK_SIZE);
|
|
memcpy(payload->req.key, key, PICOPASS_BLOCK_SIZE);
|
|
memcpy(payload->req2.key, aa2_standard_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(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!"));
|
|
repeat = false;
|
|
} else if (resp.status == PM3_ESOFT) {
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int CmdHFiClassLegRecLookUp(const char *Cmd) {
|
|
|
|
//Standalone Command Start
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf iclass legbrute",
|
|
"This command take sniffed trace data and partial raw key and bruteforces the remaining 40 bits of the raw key.",
|
|
"hf iclass legbrute --epurse feffffffffffffff --macs1 1306cad9b6c24466 --macs2 f0bf905e35f97923 --pk B4F12AADC5301225"
|
|
);
|
|
|
|
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_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); //has to be 64 as we're bruteforcing 40 bits
|
|
index = index * 1000000;
|
|
|
|
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;
|
|
}
|
|
//Standalone Command End
|
|
|
|
uint8_t CCNR[12];
|
|
uint8_t MAC_TAG[4] = {0, 0, 0, 0};
|
|
uint8_t CCNR2[12];
|
|
uint8_t MAC_TAG2[4] = {0, 0, 0, 0};
|
|
|
|
// Copy CCNR and MAC_TAG
|
|
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);
|
|
|
|
PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, 8));
|
|
PrintAndLogEx(SUCCESS, " MACS1: %s", sprint_hex(macs, 8));
|
|
PrintAndLogEx(SUCCESS, " MACS2: %s", sprint_hex(macs2, 8));
|
|
PrintAndLogEx(SUCCESS, " CCNR1: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
|
|
PrintAndLogEx(SUCCESS, " CCNR2: " _GREEN_("%s"), sprint_hex(CCNR2, sizeof(CCNR2)));
|
|
PrintAndLogEx(SUCCESS, "TAG MAC1: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
|
|
PrintAndLogEx(SUCCESS, "TAG MAC2: %s", sprint_hex(MAC_TAG2, sizeof(MAC_TAG2)));
|
|
PrintAndLogEx(SUCCESS, "Starting Key: %s", sprint_hex(startingKey, 8));
|
|
|
|
bool verified = false;
|
|
uint8_t div_key[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t generated_mac[4] = {0, 0, 0, 0};
|
|
|
|
while (!verified) {
|
|
|
|
//generate the key block
|
|
generate_key_block_inverted(startingKey, index, div_key);
|
|
|
|
//generate the relevant macs
|
|
|
|
doMAC(CCNR, div_key, generated_mac);
|
|
bool mac_match = true;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (MAC_TAG[i] != generated_mac[i]) {
|
|
mac_match = false;
|
|
}
|
|
}
|
|
|
|
if (mac_match) {
|
|
//verify this against macs2
|
|
PrintAndLogEx(WARNING, _YELLOW_("Found potentially valid RAW key ") _GREEN_("%s")_YELLOW_(" verifying it..."), sprint_hex(div_key, 8));
|
|
//generate the macs from the key and not the other way around, so we can quickly validate it
|
|
uint8_t verification_mac[4] = {0, 0, 0, 0};
|
|
doMAC(CCNR2, div_key, verification_mac);
|
|
PrintAndLogEx(INFO, "Usr Provided Mac2: " _GREEN_("%s"), sprint_hex(MAC_TAG2, sizeof(MAC_TAG2)));
|
|
PrintAndLogEx(INFO, "Verification Mac: " _GREEN_("%s"), sprint_hex(verification_mac, sizeof(verification_mac)));
|
|
bool check_values = true;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (MAC_TAG2[i] != verification_mac[i]) {
|
|
check_values = false;
|
|
}
|
|
}
|
|
if (check_values) {
|
|
PrintAndLogEx(SUCCESS, _GREEN_("CONFIRMED VALID RAW key ") _RED_("%s"), sprint_hex(div_key, 8));
|
|
PrintAndLogEx(INFO, "You can now run -> "_YELLOW_("hf iclass unhash -k %s")" <-to find the pre-images.", sprint_hex(div_key, 8));
|
|
verified = true;
|
|
} else {
|
|
PrintAndLogEx(INFO, _YELLOW_("Raw Key Invalid"));
|
|
}
|
|
|
|
}
|
|
if (index % 1000000 == 0) {
|
|
PrintAndLogEx(INFO, "Tested: " _YELLOW_("%" PRIu64)" million keys", index / 1000000);
|
|
PrintAndLogEx(INFO, "Last Generated Key Value: " _YELLOW_("%s"), sprint_hex(div_key, 8));
|
|
}
|
|
index++;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
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(INFO, _YELLOW_("This simulation assumes the card is standard keyed."));
|
|
|
|
uint8_t key[PICOPASS_BLOCK_SIZE] = {0};
|
|
uint8_t original_key[PICOPASS_BLOCK_SIZE];
|
|
|
|
uint8_t csn[8] = {0};
|
|
uint8_t new_div_key[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;
|
|
}
|
|
HFiClassCalcDivKey(csn, iClass_Key_Table[0], new_div_key, false);
|
|
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};
|
|
uint8_t zero_key_two[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];
|
|
uint8_t xorkeyblock[PICOPASS_BLOCK_SIZE] = {0};
|
|
|
|
generate_single_key_block_inverted_opt(zero_key, index, genkeyblock);
|
|
memcpy(xorkeyblock, genkeyblock, PICOPASS_BLOCK_SIZE);
|
|
|
|
for (int i = 0; i < 8 ; i++) {
|
|
key[i] = xorkeyblock[i] ^ original_key[i];
|
|
memcpy(zero_key_two, xorkeyblock, PICOPASS_BLOCK_SIZE);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
if (same_bits) {
|
|
bits_found = index;
|
|
PrintAndLogEx(SUCCESS, "Original Key: " _GREEN_("%s"), sprint_hex(original_key, sizeof(original_key)));
|
|
PrintAndLogEx(SUCCESS, "Weak Key: " _GREEN_("%s"), sprint_hex(key, sizeof(key)));
|
|
PrintAndLogEx(SUCCESS, "Key Updates Required to Weak Key: " _GREEN_("%d"), index);
|
|
PrintAndLogEx(SUCCESS, "Estimated Time: ~" _GREEN_("%d")" hours", index / 6545);
|
|
}
|
|
|
|
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 a long time. The Card must remain be on the PM3 antenna during the whole process! This process may brick the card!",
|
|
"hf iclass legrec --macs 0000000089cb984b\n"
|
|
"hf iclass legrec --macs 0000000089cb984b --index 0 --loop 100 --notest"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str1(NULL, "macs", "<hex>", "AA1 Authentication MACs"),
|
|
arg_int0(NULL, "index", "<dec>", "Where to start from to retrieve the key, default 0"),
|
|
arg_int0(NULL, "loop", "<dec>", "The number of key retrieval cycles to perform, max 10000, default 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, "est", "Estimates the key updates based on the card's CSN assuming standard key."),
|
|
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 sim = arg_get_lit(ctx, 7);
|
|
|
|
if (sim) {
|
|
CmdHFiClassLegacyRecSim();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
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 (debug || test) {
|
|
loop = 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
iclass_recover(macs, index, loop, no_first_auth, debug, test, allnight);
|
|
|
|
PrintAndLogEx(WARNING, _YELLOW_("If the process completed successfully, you can now run 'hf iclass legbrute' with the partial key found."));
|
|
|
|
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;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "Diversified key... %s", sprint_hex_inrow(div_key, sizeof(div_key)));
|
|
|
|
invert_hash0(div_key);
|
|
|
|
PrintAndLogEx(SUCCESS, "You can now retrieve the master key by cracking DES with hashcat!");
|
|
PrintAndLogEx(SUCCESS, "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",
|
|
"This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.",
|
|
"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"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_str0("f", "file", "<fn>", "Dictionary file with default iclass keys"),
|
|
arg_str1(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"),
|
|
arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
|
|
arg_str1(NULL, "macs", "<hex>", "MACs"),
|
|
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_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
|
|
|
bool use_vb6kdf = arg_get_lit(ctx, 7);
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
|
|
bool use_elite = arg_get_lit(ctx, 5);
|
|
bool use_raw = arg_get_lit(ctx, 6);
|
|
if (use_vb6kdf) {
|
|
use_elite = true;
|
|
} else {
|
|
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
}
|
|
|
|
int csn_len = 0;
|
|
uint8_t csn[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 2, csn, &csn_len);
|
|
|
|
if (csn_len > 0) {
|
|
if (csn_len != 8) {
|
|
PrintAndLogEx(ERR, "CSN is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
int epurse_len = 0;
|
|
uint8_t epurse[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 3, epurse, &epurse_len);
|
|
|
|
if (epurse_len > 0) {
|
|
if (epurse_len != 8) {
|
|
PrintAndLogEx(ERR, "ePurse is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
int macs_len = 0;
|
|
uint8_t macs[8] = {0};
|
|
CLIGetHexWithReturn(ctx, 4, macs, &macs_len);
|
|
|
|
if (macs_len > 0) {
|
|
if (macs_len != 8) {
|
|
PrintAndLogEx(ERR, "MAC is incorrect length");
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
uint8_t CCNR[12];
|
|
uint8_t MAC_TAG[4] = { 0, 0, 0, 0 };
|
|
|
|
// Stupid copy.. CCNR is a combo of epurse and reader nonce
|
|
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) {
|
|
// 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) {
|
|
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 " _YELLOW_("%s") " key...", "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(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[8];
|
|
uint8_t cc_nr[12];
|
|
memcpy(csn, targ->csn, sizeof(csn));
|
|
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
|
|
|
uint8_t key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
for (uint32_t i = idx; i < keycnt; i += iclass_tc) {
|
|
|
|
memcpy(key, keys + 8 * i, 8);
|
|
|
|
pthread_mutex_lock(&generator_mutex);
|
|
if (use_raw)
|
|
memcpy(div_key, key, 8);
|
|
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[8];
|
|
uint8_t cc_nr[12];
|
|
memcpy(csn, targ->csn, sizeof(csn));
|
|
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
|
|
|
uint8_t div_key[8] = {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, 8);
|
|
|
|
pthread_mutex_lock(&generator_mutex);
|
|
if (use_raw)
|
|
memcpy(div_key, list[i].key, 8);
|
|
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)
|
|
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));
|
|
PrintAndLogEx(SUCCESS, "input permuted key | %s \n", sprint_hex(data, len));
|
|
permute_rev(data, len, key);
|
|
PrintAndLogEx(SUCCESS, " unpermuted key | %s \n", sprint_hex(key, len));
|
|
shave(key, len);
|
|
PrintAndLogEx(SUCCESS, " key | %s \n", sprint_hex(key, len));
|
|
free(key);
|
|
}
|
|
static void generate(uint8_t *data, uint8_t len) {
|
|
uint8_t *key = calloc(len, sizeof(uint8_t));
|
|
uint8_t *pkey = calloc(len, sizeof(uint8_t));
|
|
PrintAndLogEx(SUCCESS, " input key | %s \n", sprint_hex(data, len));
|
|
permute(data, len, pkey);
|
|
PrintAndLogEx(SUCCESS, "permuted key | %s \n", sprint_hex(pkey, len));
|
|
simple_crc(pkey, len, key);
|
|
PrintAndLogEx(SUCCESS, " CRC'ed key | %s \n", sprint_hex(key, len));
|
|
free(key);
|
|
free(pkey);
|
|
}
|
|
|
|
static int CmdHFiClassPermuteKey(const char *Cmd) {
|
|
|
|
uint8_t key[8] = {0};
|
|
uint8_t data[16] = {0};
|
|
int len = 0;
|
|
|
|
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);
|
|
CLIGetHexWithReturn(ctx, 2, data, &len);
|
|
CLIParserFree(ctx);
|
|
|
|
memcpy(key, data, 8);
|
|
|
|
if (isReverse) {
|
|
generate_rev(data, len);
|
|
uint8_t key_std_format[8] = {0};
|
|
permutekey_rev(key, key_std_format);
|
|
PrintAndLogEx(SUCCESS, "Standard NIST format key " _YELLOW_("%s") " \n", sprint_hex(key_std_format, 8));
|
|
} else {
|
|
generate(data, len);
|
|
uint8_t key_iclass_format[8] = {0};
|
|
permutekey(key, key_iclass_format);
|
|
PrintAndLogEx(SUCCESS, "HID permuted iCLASS format: %s \n", sprint_hex(key_iclass_format, 8));
|
|
}
|
|
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",
|
|
"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 --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\n"
|
|
"When using emulator you have to first load a credential into 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_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);
|
|
bool use_emulator_memory = arg_get_lit(ctx, 11);
|
|
|
|
bool auth = false;
|
|
uint8_t key[8] = {0};
|
|
|
|
// If we use emulator memory skip key requirement
|
|
if (use_emulator_memory == false) {
|
|
if (key_nr < 0) {
|
|
PrintAndLogEx(ERR, "Missing required arg for --ki or --emu");
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
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, 3);
|
|
bool elite = arg_get_lit(ctx, 4);
|
|
bool rawkey = arg_get_lit(ctx, 5);
|
|
|
|
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, 6, 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, 7, 0);
|
|
card.CardNumber = arg_get_u32_def(ctx, 8, 0);
|
|
card.IssueLevel = arg_get_u32_def(ctx, 9, 0);
|
|
|
|
char format[16] = {0};
|
|
int format_len = 0;
|
|
|
|
CLIParamStrToBuf(arg_get_str(ctx, 10), (uint8_t *)format, sizeof(format), &format_len);
|
|
|
|
bool shallow_mod = arg_get_lit(ctx, 12);
|
|
bool verbose = arg_get_lit(ctx, 13);
|
|
|
|
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 (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 int CmdHFiClassSAM(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, but ensure that epurse will stay unchanged\n"
|
|
"hf iclass sam --break-on-nr-mac -> get Nr-MAC for extracting encrypted SIO\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-on-nr-mac", "stop tag interaction on nr-mac"),
|
|
arg_lit0("p", "prevent-epurse-update", "fake epurse update"),
|
|
arg_lit0(NULL, "shallow", "shallow mod"),
|
|
arg_strx0("d", "data", "<hex>", "DER encoded command to send to SAM"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
|
|
bool verbose = arg_get_lit(ctx, 1);
|
|
bool disconnectAfter = !arg_get_lit(ctx, 2);
|
|
bool skipDetect = arg_get_lit(ctx, 3);
|
|
bool decodeTLV = arg_get_lit(ctx, 4);
|
|
bool breakOnNrMac = arg_get_lit(ctx, 5);
|
|
bool preventEpurseUpdate = arg_get_lit(ctx, 6);
|
|
bool shallow_mod = arg_get_lit(ctx, 7);
|
|
|
|
uint8_t flags = 0;
|
|
if (disconnectAfter) flags |= BITMASK(0);
|
|
if (skipDetect) flags |= BITMASK(1);
|
|
if (breakOnNrMac) flags |= BITMASK(2);
|
|
if (preventEpurseUpdate) flags |= BITMASK(3);
|
|
if (shallow_mod) flags |= BITMASK(4);
|
|
|
|
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;
|
|
}
|
|
|
|
clearCommandBuffer();
|
|
SendCommandNG(CMD_HF_SAM_PICOPASS, data, cmdlen + 1);
|
|
PacketResponseNG resp;
|
|
if (WaitForResponseTimeout(CMD_HF_SAM_PICOPASS, &resp, 4000) == false) {
|
|
PrintAndLogEx(WARNING, "SAM timeout");
|
|
return PM3_ETIMEOUT;
|
|
}
|
|
|
|
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 (breakOnNrMac && 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 -k \"%s\" --nr", sprint_hex_inrow(d + 1, 8));
|
|
}
|
|
} else {
|
|
print_hex(d, resp.length);
|
|
}
|
|
if (decodeTLV) {
|
|
asn1_print(d, d[1] + 2, " ");
|
|
}
|
|
|
|
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"},
|
|
{"-----------", 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", CmdHFiClassLegRecLookUp, 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"},
|
|
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
|
|
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
|
|
{"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 tests"},
|
|
{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)
|
|
};
|
|
|
|
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: %s debit key ( hidden )", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
|
|
}
|
|
|
|
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: %s credit key ( hidden )", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
|
|
}
|
|
|
|
|
|
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 (HF14B_picopass_reader(false, false)) {
|
|
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)"));
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("NEW Silicon (No 14b support)"));
|
|
}
|
|
if (legacy) {
|
|
|
|
int res = PM3_ESOFT;
|
|
uint8_t key_type = 0x88; // debit key
|
|
|
|
uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0};
|
|
// we take all raw bytes from response
|
|
memcpy(dump, p_response, sizeof(picopass_hdr_t));
|
|
|
|
uint8_t key[8] = {0};
|
|
for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) {
|
|
|
|
memcpy(key, iClass_Key_Table[i], sizeof(key));
|
|
res = iclass_read_block_ex(key, 6, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 6), false);
|
|
if (res == PM3_SUCCESS) {
|
|
PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (res == PM3_SUCCESS) {
|
|
res = iclass_read_block_ex(key, 7, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false);
|
|
if (res == PM3_SUCCESS) {
|
|
|
|
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;
|
|
}
|