Skip to content

Commit

Permalink
Picopass read card using nr-mac (#79)
Browse files Browse the repository at this point in the history
Co-authored-by: あく <alleteam@gmail.com>
  • Loading branch information
bettse and skotopes committed Dec 6, 2023
1 parent 83535be commit 6ff62d7
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 44 deletions.
1 change: 1 addition & 0 deletions picopass_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum PicopassCustomEvent {
PicopassCustomEventDictAttackUpdateView,
PicopassCustomEventLoclassGotMac,
PicopassCustomEventLoclassGotStandardKey,
PicopassCustomEventNrMacSaved,

PicopassCustomEventPollerSuccess,
PicopassCustomEventPollerFail,
Expand Down
90 changes: 77 additions & 13 deletions protocol/picopass_listener.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "picopass_i.h"
#include "picopass_listener_i.h"
#include "picopass_keys.h"

Expand Down Expand Up @@ -299,6 +300,61 @@ PicopassListenerCommand
return command;
}

PicopassListenerCommand picopass_listener_save_mac(PicopassListener* instance, uint8_t* rx_data) {
PicopassListenerCommand command = PicopassListenerCommandSilent;
Picopass* picopass = instance->context;

PicopassDevice* dev = picopass->dev;

const uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
const uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;

FuriString* temp_str = furi_string_alloc();
FuriString* filename = furi_string_alloc();
FlipperFormat* file = flipper_format_file_alloc(dev->storage);

for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
furi_string_cat_printf(filename, "%02x", csn[i]);
}
furi_string_cat_printf(filename, "_");
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
furi_string_cat_printf(filename, "%02x", epurse[i]);
}

furi_string_printf(
temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");
do {
// Open file
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;

if(!flipper_format_write_hex(file, "NR-MAC", rx_data + 1, PICOPASS_BLOCK_LEN)) break;

FURI_LOG_D(
TAG,
"Saved nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
// Skip command byte [0]
rx_data[1],
rx_data[2],
rx_data[3],
rx_data[4],
rx_data[5],
rx_data[6],
rx_data[7],
rx_data[8]);

notification_message(picopass->notifications, &sequence_double_vibro);
command = PicopassListenerCommandStop;
view_dispatcher_send_custom_event(
picopass->view_dispatcher, PicopassCustomEventNrMacSaved);
} while(0);

furi_string_free(temp_str);
furi_string_free(filename);
flipper_format_free(file);

return command;
}

PicopassListenerCommand
picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
PicopassListenerCommand command = PicopassListenerCommandSilent;
Expand All @@ -310,23 +366,31 @@ PicopassListenerCommand
// Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
uint8_t rx_data[9] = {};
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN);

#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
if(memcmp(&rx_data[5], rmac, 4)) {
// Bad MAC from reader, do not send a response.
FURI_LOG_I(TAG, "Got bad MAC from reader");
// Reset the cipher state since we don't do it in READCHECK
picopass_listener_init_cipher_state(instance);
if(no_key) {
// We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
command = picopass_listener_save_mac(instance, rx_data);
break;
}
} else {
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);

#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN)) {
// Bad MAC from reader, do not send a response.
FURI_LOG_I(TAG, "Got bad MAC from reader");
// Reset the cipher state since we don't do it in READCHECK
picopass_listener_init_cipher_state(instance);
break;
}
#endif

bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
if(error != NfcErrorNone) {
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
break;
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
if(error != NfcErrorNone) {
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
break;
}
}

command = PicopassListenerCommandProcessed;
Expand Down
133 changes: 130 additions & 3 deletions protocol/picopass_poller.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "picopass_i.h"
#include "picopass_poller_i.h"

#include "../loclass/optimized_cipher.h"
Expand Down Expand Up @@ -95,7 +96,7 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);

PicopassBlock block = {};
error = picopass_poller_read_block(instance, 1, &block);
error = picopass_poller_read_block(instance, PICOPASS_CONFIG_BLOCK_INDEX, &block);
if(error != PicopassErrorNone) {
instance->state = PicopassPollerStateFail;
break;
Expand All @@ -116,6 +117,27 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);

error = picopass_poller_read_block(instance, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, &block);
if(error != PicopassErrorNone) {
instance->state = PicopassPollerStateFail;
break;
}
memcpy(
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
block.data,
sizeof(PicopassBlock));
FURI_LOG_D(
TAG,
"epurse %02x%02x%02x%02x%02x%02x%02x%02x",
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[0],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[1],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[2],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[3],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[4],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[5],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[6],
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[7]);

error = picopass_poller_read_block(instance, 5, &block);
if(error != PicopassErrorNone) {
instance->state = PicopassPollerStateFail;
Expand Down Expand Up @@ -165,10 +187,114 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {

if(instance->data->pacs.se_enabled) {
FURI_LOG_D(TAG, "SE enabled");
instance->state = PicopassPollerStateFail;
instance->state = PicopassPollerStateNrMacAuth;
} else {
instance->state = PicopassPollerStateAuth;
}
return command;
}

NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
NfcCommand command = NfcCommandContinue;
Picopass* picopass = instance->context;
PicopassDevice* dev = picopass->dev;

uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;

FuriString* temp_str = furi_string_alloc();
FuriString* filename = furi_string_alloc();
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
PicopassMac mac = {};

for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
furi_string_cat_printf(filename, "%02x", csn[i]);
}
furi_string_cat_printf(filename, "_");
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
furi_string_cat_printf(filename, "%02x", epurse[i]);
}

furi_string_printf(
temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");

FURI_LOG_D(TAG, "Looking for %s", furi_string_get_cstr(temp_str));
uint8_t nr_mac[PICOPASS_BLOCK_LEN];

// Presume failure unless all steps are successful and the state is made "read block"
instance->state = PicopassPollerStateFail;
do {
//check for file
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break;
// FURI_LOG_D(TAG, "Found %s", furi_string_get_cstr(temp_str));

furi_string_printf(temp_str, "NR-MAC");
if(!flipper_format_read_hex(
file, furi_string_get_cstr(temp_str), nr_mac, PICOPASS_BLOCK_LEN))
break;
memcpy(mac.data, nr_mac + 4, PICOPASS_MAC_LEN);
/*
FURI_LOG_D(
TAG,
"Read nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
nr_mac[0],
nr_mac[1],
nr_mac[2],
nr_mac[3],
nr_mac[4],
nr_mac[5],
nr_mac[6],
nr_mac[7]);
FURI_LOG_D(
TAG, "MAC: %02x %02x %02x %02x", mac.data[0], mac.data[1], mac.data[2], mac.data[3]);
*/

uint8_t ccnr[12] = {};
PicopassReadCheckResp read_check_resp = {};
PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
if(error == PicopassErrorTimeout) {
instance->event.type = PicopassPollerEventTypeCardLost;
instance->callback(instance->event, instance->context);
instance->state = PicopassPollerStateDetect;
break;
} else if(error != PicopassErrorNone) {
FURI_LOG_E(TAG, "Read check failed: %d", error);
break;
}
memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0

/*
FURI_LOG_D(
TAG,
"CCNR: %02x %02x %02x %02x %02x %02x %02x %02x",
ccnr[0],
ccnr[1],
ccnr[2],
ccnr[3],
ccnr[4],
ccnr[5],
ccnr[6],
ccnr[7]);
*/

//use mac
PicopassCheckResp check_resp = {};
error = picopass_poller_check(instance, nr_mac, &mac, &check_resp);
if(error == PicopassErrorNone) {
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
if(instance->mode == PicopassPollerModeRead) {
picopass_poller_prepare_read(instance);
instance->state = PicopassPollerStateReadBlock;
// Set to non-zero keys to allow emulation
memset(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xff, PICOPASS_BLOCK_LEN);
memset(instance->data->AA1[PICOPASS_SECURE_KC_BLOCK_INDEX].data, 0xff, PICOPASS_BLOCK_LEN);
}
}

} while(false);
furi_string_free(temp_str);
furi_string_free(filename);
flipper_format_free(file);

return command;
}
Expand Down Expand Up @@ -234,7 +360,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
loclass_opt_doReaderMAC(ccnr, div_key, mac.data);

PicopassCheckResp check_resp = {};
error = picopass_poller_check(instance, &mac, &check_resp);
error = picopass_poller_check(instance, NULL, &mac, &check_resp);
if(error == PicopassErrorNone) {
FURI_LOG_I(TAG, "Found key");
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
Expand Down Expand Up @@ -457,6 +583,7 @@ static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPo
[PicopassPollerStateSelect] = picopass_poller_select_handler,
[PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
[PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
[PicopassPollerStateNrMacAuth] = picopass_poller_nr_mac_auth,
[PicopassPollerStateAuth] = picopass_poller_auth_handler,
[PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
[PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
Expand Down
9 changes: 7 additions & 2 deletions protocol/picopass_poller_i.c
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,20 @@ PicopassError

PicopassError picopass_poller_check(
PicopassPoller* instance,
uint8_t* nr,
PicopassMac* mac,
PicopassCheckResp* check_resp) {
PicopassError ret = PicopassErrorNone;
uint8_t null_arr[4] = {};

do {
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
uint8_t null_arr[4] = {};
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
if(nr) {
bit_buffer_append_bytes(instance->tx_buffer, nr, 4);
} else {
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
}
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));

NfcError error = nfc_poller_trx(
Expand Down
2 changes: 2 additions & 0 deletions protocol/picopass_poller_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef enum {
PicopassPollerStateSelect,
PicopassPollerStatePreAuth,
PicopassPollerStateCheckSecurity,
PicopassPollerStateNrMacAuth,
PicopassPollerStateAuth,
PicopassPollerStateReadBlock,
PicopassPollerStateWriteBlock,
Expand Down Expand Up @@ -75,6 +76,7 @@ PicopassError

PicopassError picopass_poller_check(
PicopassPoller* instance,
uint8_t* nr,
PicopassMac* mac,
PicopassCheckResp* check_resp);

Expand Down
Loading

0 comments on commit 6ff62d7

Please sign in to comment.