Picopass: factory key support, app rename and move to NFC category, minor code cleanup (#2417)

* message on successful card write
* auth using factory key
* auth using factory default
* factory default screen
* write standard iclass key
* pass block explicitly
* Fix array indexing, add empty detection
* PicoPass: rename app and move to NFC group, minor code cleanup

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Eric Betts 2023-02-25 23:59:50 -08:00 committed by GitHub
parent eaf965c66f
commit 03f889962b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 367 additions and 27 deletions

View File

@ -1,6 +1,6 @@
App(
appid="picopass",
name="PicoPass Reader",
name="PicoPass",
apptype=FlipperAppType.EXTERNAL,
targets=["f7"],
entry_point="picopass_app",
@ -11,7 +11,7 @@ App(
stack_size=4 * 1024,
order=30,
fap_icon="125_10px.png",
fap_category="Tools",
fap_category="NFC",
fap_libs=["mbedtls"],
fap_private_libs=[
Lib(

View File

@ -171,6 +171,16 @@ void picopass_show_loading_popup(void* context, bool show) {
}
}
bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) {
bool result = size > 0;
while(size > 0) {
result &= (*data == pattern);
data++;
size--;
}
return result;
}
int32_t picopass_app(void* p) {
UNUSED(p);
Picopass* picopass = picopass_alloc();

View File

@ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r
record->CardNumber = (bot >> 1) & 0xFFFF;
record->FacilityCode = (bot >> 17) & 0xFF;
FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber);
FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
record->valid = true;
} else {
record->CardNumber = 0;

View File

@ -22,6 +22,7 @@
#define PICOPASS_KD_BLOCK_INDEX 3
#define PICOPASS_KC_BLOCK_INDEX 4
#define PICOPASS_AIA_BLOCK_INDEX 5
#define PICOPASS_PACS_CFG_BLOCK_INDEX 6
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
#define PICOPASS_APP_EXTENSION ".picopass"

View File

@ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass);
void picopass_blink_stop(Picopass* picopass);
void picopass_show_loading_popup(void* context, bool show);
/** Check if memory is set to pattern
*
* @warning zero size will return false
*
* @param[in] data Pointer to the byte array
* @param[in] pattern The pattern
* @param[in] size The byte array size
*
* @return True if memory is set to pattern, false otherwise
*/
bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size);

View File

@ -5,7 +5,8 @@
#define TAG "PicopassWorker"
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
static void picopass_worker_enable_field() {
furi_hal_nfc_ll_txrx_on();
@ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) {
return rfalPicoPassPollerCheck(mac, &chkRes);
}
static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) {
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(csn, picopass_factory_debit_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
return rfalPicoPassPollerCheck(mac, &chkRes);
}
static ReturnCode picopass_auth_dict(
uint8_t* csn,
PicopassPacs* pacs,
@ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict(
ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
ReturnCode err;
FURI_LOG_E(TAG, "Trying standard legacy key");
FURI_LOG_I(TAG, "Trying standard legacy key");
err = picopass_auth_standard(
AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
if(err == ERR_NONE) {
memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
return ERR_NONE;
}
FURI_LOG_E(TAG, "Starting user dictionary attack");
FURI_LOG_I(TAG, "Trying factory default key");
err = picopass_auth_factory(
AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
if(err == ERR_NONE) {
memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN);
return ERR_NONE;
}
FURI_LOG_I(TAG, "Starting user dictionary attack");
err = picopass_auth_dict(
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
pacs,
@ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
return ERR_NONE;
}
FURI_LOG_E(TAG, "Starting in-built dictionary attack");
FURI_LOG_I(TAG, "Starting system dictionary attack");
err = picopass_auth_dict(
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
pacs,
@ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) {
return ERR_NONE;
}
ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) {
rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t div_key[8] = {0};
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerIdentify(&idRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
return err;
}
err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
return err;
}
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, pacs->key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
return err;
}
FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo);
uint8_t data[9] = {
blockNo,
newBlock[0],
newBlock[1],
newBlock[2],
newBlock[3],
newBlock[4],
newBlock[5],
newBlock[6],
newBlock[7]};
loclass_doMAC_N(data, sizeof(data), div_key, mac);
FURI_LOG_D(
TAG,
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
blockNo,
data[1],
data[2],
data[3],
data[4],
data[5],
data[6],
data[7],
data[8],
mac[0],
mac[1],
mac[2],
mac[3]);
err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
return err;
}
return ERR_NONE;
}
int32_t picopass_worker_task(void* context) {
PicopassWorker* picopass_worker = context;
@ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) {
picopass_worker_detect(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWrite) {
picopass_worker_write(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
picopass_worker_write_standard_key(picopass_worker);
}
picopass_worker_disable_field(ERR_NONE);
@ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
}
// Thank you proxmark!
pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
if(pacs->se_enabled) {
FURI_LOG_D(TAG, "SE enabled");
@ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) {
furi_delay_ms(100);
}
}
void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) {
PicopassDeviceData* dev_data = picopass_worker->dev_data;
PicopassBlock* AA1 = dev_data->AA1;
PicopassPacs* pacs = &dev_data->pacs;
ReturnCode err;
PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
uint8_t fuses = configBlock[7];
uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data;
uint8_t newKey[PICOPASS_BLOCK_LEN] = {0};
loclass_diversifyKey(csn, picopass_iclass_key, newKey);
if((fuses & 0x80) == 0x80) {
FURI_LOG_D(TAG, "Plain write for personalized mode key change");
} else {
FURI_LOG_D(TAG, "XOR write for application mode key change");
// XOR when in application mode
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
newKey[i] ^= oldKey[i];
}
}
while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
if(picopass_detect_card(1000) == ERR_NONE) {
err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_write_block error %d", err);
nextState = PicopassWorkerEventFail;
}
// Notify caller and exit
if(picopass_worker->callback) {
picopass_worker->callback(nextState, picopass_worker->context);
}
break;
}
furi_delay_ms(100);
}
}

View File

@ -12,6 +12,7 @@ typedef enum {
// Main worker states
PicopassWorkerStateDetect,
PicopassWorkerStateWrite,
PicopassWorkerStateWriteStandardKey,
// Transition
PicopassWorkerStateStop,
} PicopassWorkerState;

View File

@ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context);
void picopass_worker_detect(PicopassWorker* picopass_worker);
void picopass_worker_write(PicopassWorker* picopass_worker);
void picopass_worker_write_standard_key(PicopassWorker* picopass_worker);

View File

@ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete)
ADD_SCENE(picopass, delete_success, DeleteSuccess)
ADD_SCENE(picopass, write_card, WriteCard)
ADD_SCENE(picopass, write_card_success, WriteCardSuccess)
ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess)
ADD_SCENE(picopass, write_key, WriteKey)

View File

@ -1,6 +1,8 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
Picopass* picopass = context;
@ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventWorkerExit) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
if(memcmp(
picopass->dev->dev_data.pacs.key,
picopass_factory_key_check,
PICOPASS_BLOCK_LEN) == 0) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
} else {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
}
consumed = true;
}
}

View File

@ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback(
void picopass_scene_read_card_success_on_enter(void* context) {
Picopass* picopass = context;
FuriString* csn_str = furi_string_alloc_set("CSN:");
FuriString* credential_str = furi_string_alloc();
FuriString* wiegand_str = furi_string_alloc();
@ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) {
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget;
uint8_t csn[PICOPASS_BLOCK_LEN];
memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN);
uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
furi_string_cat_printf(csn_str, "%02X ", csn[i]);
}
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);
bool empty =
picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
if(no_key) {
furi_string_cat_printf(wiegand_str, "Read Failed");
if(pacs->se_enabled) {
furi_string_cat_printf(credential_str, "SE enabled");
}
} else if(empty) {
furi_string_cat_printf(wiegand_str, "Empty");
} else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
furi_string_cat_printf(wiegand_str, "Invalid PACS");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
if(pacs->se_enabled) {
furi_string_cat_printf(credential_str, "SE enabled");
}
} else {
size_t bytesLength = 1 + pacs->record.bitLength / 8;
furi_string_set(credential_str, "");
@ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) {
}
}
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_button_element(
widget,
GuiButtonTypeRight,
@ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) {
picopass);
}
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_string_element(
widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
widget_add_string_element(

View File

@ -0,0 +1,78 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_read_factory_success_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
furi_assert(context);
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_read_factory_success_on_enter(void* context) {
Picopass* picopass = context;
FuriString* title = furi_string_alloc_set("Factory Default");
FuriString* subtitle = furi_string_alloc_set("");
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Send notification
notification_message(picopass->notifications, &sequence_success);
// Setup view
Widget* widget = picopass->widget;
//PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
uint8_t fuses = configBlock[7];
if((fuses & 0x80) == 0x80) {
furi_string_cat_printf(subtitle, "Personalization mode");
} else {
furi_string_cat_printf(subtitle, "Application mode");
}
widget_add_button_element(
widget,
GuiButtonTypeCenter,
"Write Standard iClass Key",
picopass_scene_read_factory_success_widget_callback,
picopass);
widget_add_string_element(
widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title));
widget_add_string_element(
widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle));
furi_string_free(title);
furi_string_free(subtitle);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeCenter) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
consumed = true;
}
}
return consumed;
}
void picopass_scene_read_factory_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
widget_reset(picopass->widget);
}

View File

@ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback(
void picopass_scene_write_card_success_on_enter(void* context) {
Picopass* picopass = context;
Widget* widget = picopass->widget;
FuriString* str = furi_string_alloc_set("Write Success!");
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
@ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) {
picopass_scene_write_card_success_widget_callback,
picopass);
widget_add_button_element(
widget,
GuiButtonTypeRight,
"Menu",
picopass_scene_write_card_success_widget_callback,
picopass);
widget_add_string_element(
widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str));
furi_string_free(str);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}

View File

@ -0,0 +1,53 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
}
void picopass_scene_write_key_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcSave);
// Setup view
Popup* popup = picopass->popup;
popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
// Start worker
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
picopass_worker_start(
picopass->worker,
PicopassWorkerStateWriteStandardKey,
&picopass->dev->dev_data,
picopass_write_key_worker_callback,
picopass);
picopass_blink_start(picopass);
}
bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventWorkerExit) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
consumed = true;
}
}
return consumed;
}
void picopass_scene_write_key_on_exit(void* context) {
Picopass* picopass = context;
// Stop worker
picopass_worker_stop(picopass->worker);
// Clear view
popup_reset(picopass->popup);
picopass_blink_stop(picopass);
}