diff --git a/nfc_magic/.catalog/changelog.txt b/nfc_magic/.catalog/changelog.txt new file mode 100644 index 00000000..4eec4817 --- /dev/null +++ b/nfc_magic/.catalog/changelog.txt @@ -0,0 +1,4 @@ +## 1.1 + - Rework application with new NFC API +## 1.0 + - Initial release diff --git a/nfc_magic/application.fam b/nfc_magic/application.fam index cb89e71d..64d3cbc8 100644 --- a/nfc_magic/application.fam +++ b/nfc_magic/application.fam @@ -10,7 +10,7 @@ App( ], stack_size=4 * 1024, fap_description="Application for writing to NFC tags with modifiable sector 0", - fap_version="1.0", + fap_version="1.1", fap_icon="assets/125_10px.png", fap_category="NFC", fap_private_libs=[ diff --git a/nfc_rfid_detector/.catalog/changelog.md b/nfc_rfid_detector/.catalog/changelog.md index 8a932cee..64420cfc 100644 --- a/nfc_rfid_detector/.catalog/changelog.md +++ b/nfc_rfid_detector/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 1.2 + - Rework application with new NFC API ## 1.1 - App rename, removed About screen ## 1.0 diff --git a/nfc_rfid_detector/application.fam b/nfc_rfid_detector/application.fam index 55595204..2eec9eab 100644 --- a/nfc_rfid_detector/application.fam +++ b/nfc_rfid_detector/application.fam @@ -7,7 +7,7 @@ App( requires=["gui"], stack_size=4 * 1024, fap_description="Identify the reader type: NFC (13 MHz) and/or RFID (125 KHz).", - fap_version="1.1", + fap_version="1.2", fap_icon="nfc_rfid_detector_10px.png", fap_category="Tools", fap_icon_assets="images", diff --git a/picopass/.catalog/changelog.md b/picopass/.catalog/changelog.md index 800263d9..b4391f6e 100644 --- a/picopass/.catalog/changelog.md +++ b/picopass/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 1.7 + - Rework application with new NFC API ## 1.6 - Faster loclass response collection - Save as LF for all bit lengths diff --git a/picopass/application.fam b/picopass/application.fam index 0754287f..6f51f30c 100644 --- a/picopass/application.fam +++ b/picopass/application.fam @@ -10,7 +10,7 @@ App( ], stack_size=4 * 1024, fap_description="App to communicate with NFC tags using the PicoPass(iClass) format", - fap_version="1.6", + fap_version="1.7", fap_icon="125_10px.png", fap_category="NFC", fap_libs=["mbedtls"], diff --git a/picopass/picopass.c b/picopass/picopass.c index 5448a58f..73b77943 100644 --- a/picopass/picopass.c +++ b/picopass/picopass.c @@ -23,7 +23,6 @@ void picopass_tick_event_callback(void* context) { Picopass* picopass_alloc() { Picopass* picopass = malloc(sizeof(Picopass)); - picopass->worker = picopass_worker_alloc(); picopass->view_dispatcher = view_dispatcher_alloc(); picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass); view_dispatcher_enable_queue(picopass->view_dispatcher); @@ -35,6 +34,8 @@ Picopass* picopass_alloc() { view_dispatcher_set_tick_event_callback( picopass->view_dispatcher, picopass_tick_event_callback, 100); + picopass->nfc = nfc_alloc(); + // Picopass device picopass->dev = picopass_device_alloc(); @@ -100,6 +101,8 @@ void picopass_free(Picopass* picopass) { picopass_device_free(picopass->dev); picopass->dev = NULL; + nfc_free(picopass->nfc); + // Submenu view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); submenu_free(picopass->submenu); @@ -130,10 +133,6 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoclass); loclass_free(picopass->loclass); - // Worker - picopass_worker_stop(picopass->worker); - picopass_worker_free(picopass->worker); - // View Dispatcher view_dispatcher_free(picopass->view_dispatcher); diff --git a/picopass/picopass_device.c b/picopass/picopass_device.c index 9b61ba4b..0d5c74f4 100644 --- a/picopass/picopass_device.c +++ b/picopass/picopass_device.c @@ -225,8 +225,8 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo } if(!block_read) break; - if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break; - if(picopass_device_parse_wiegand(pacs->credential, pacs) != ERR_NONE) break; + picopass_device_parse_credential(AA1, pacs); + picopass_device_parse_wiegand(pacs->credential, pacs); parsed = true; } while(false); @@ -343,7 +343,7 @@ void picopass_device_set_loading_callback( dev->loading_cb_ctx = context; } -ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { +void picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { uint8_t key[32] = {0}; memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); mbedtls_des3_context ctx; @@ -351,35 +351,20 @@ ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { mbedtls_des3_set2key_dec(&ctx, key); mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); mbedtls_des3_free(&ctx); - return ERR_NONE; } -ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { - ReturnCode err; - +void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { pacs->biometrics = AA1[6].data[4]; pacs->pin_length = AA1[6].data[6] & 0x0F; pacs->encryption = AA1[6].data[7]; if(pacs->encryption == PicopassDeviceEncryption3DES) { FURI_LOG_D(TAG, "3DES Encrypted"); - err = picopass_device_decrypt(AA1[7].data, pacs->credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } + picopass_device_decrypt(AA1[7].data, pacs->credential); - err = picopass_device_decrypt(AA1[8].data, pacs->pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } + picopass_device_decrypt(AA1[8].data, pacs->pin0); - err = picopass_device_decrypt(AA1[9].data, pacs->pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } + picopass_device_decrypt(AA1[9].data, pacs->pin1); } else if(pacs->encryption == PicopassDeviceEncryptionNone) { FURI_LOG_D(TAG, "No Encryption"); memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); @@ -392,11 +377,9 @@ ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pa } pacs->sio = (AA1[10].data[0] == 0x30); // rough check - - return ERR_NONE; } -ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) { +void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) { uint32_t* halves = (uint32_t*)credential; if(halves[0] == 0) { uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); @@ -413,8 +396,6 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs swapped = swapped ^ sentinel; memcpy(credential, &swapped, sizeof(uint64_t)); FURI_LOG_D(TAG, "PACS: (%d) %016llx", pacs->bitLength, swapped); - - return ERR_NONE; } bool picopass_device_hid_csn(PicopassDevice* dev) { diff --git a/picopass/picopass_device.h b/picopass/picopass_device.h index 588ae819..b46f2e6e 100644 --- a/picopass/picopass_device.h +++ b/picopass/picopass_device.h @@ -102,7 +102,6 @@ typedef struct { typedef struct { PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT]; PicopassPacs pacs; - IclassEliteDictAttackData iclass_elite_dict_attack_data; } PicopassDeviceData; typedef struct { @@ -147,6 +146,6 @@ void picopass_device_set_loading_callback( PicopassLoadingCallback callback, void* context); -ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); -ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs); +void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); +void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs); bool picopass_device_hid_csn(PicopassDevice* dev); diff --git a/picopass/picopass_i.h b/picopass/picopass_i.h index 62ff7442..2dee5f28 100644 --- a/picopass/picopass_i.h +++ b/picopass/picopass_i.h @@ -1,7 +1,6 @@ #pragma once #include "picopass.h" -#include "picopass_worker.h" #include "picopass_device.h" #include "rfal_picopass.h" @@ -29,8 +28,17 @@ #include #include +#include +#include +#include "protocol/picopass_poller.h" +#include "protocol/picopass_listener.h" + #define PICOPASS_TEXT_STORE_SIZE 128 +#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") +#define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt") +#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") + enum PicopassCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 PicopassCustomEventReserved = 100, @@ -40,6 +48,12 @@ enum PicopassCustomEvent { PicopassCustomEventByteInputDone, PicopassCustomEventTextInputDone, PicopassCustomEventDictAttackSkip, + PicopassCustomEventDictAttackUpdateView, + PicopassCustomEventLoclassGotMac, + PicopassCustomEventLoclassGotStandardKey, + + PicopassCustomEventPollerSuccess, + PicopassCustomEventPollerFail, }; typedef enum { @@ -47,14 +61,34 @@ typedef enum { EventTypeKey, } EventType; +typedef struct { + const char* name; + uint16_t total_keys; + uint16_t current_key; + bool card_detected; +} PicopassDictAttackContext; + +typedef struct { + uint8_t key_to_write[PICOPASS_BLOCK_LEN]; + bool is_elite; +} PicopassWriteKeyContext; + +typedef struct { + size_t macs_collected; +} PicopassLoclassContext; + struct Picopass { - PicopassWorker* worker; ViewDispatcher* view_dispatcher; Gui* gui; NotificationApp* notifications; SceneManager* scene_manager; PicopassDevice* dev; + Nfc* nfc; + PicopassPoller* poller; + PicopassListener* listener; + NfcDict* dict; + char text_store[PICOPASS_TEXT_STORE_SIZE + 1]; FuriString* text_box_store; uint8_t byte_input_store[PICOPASS_BLOCK_LEN]; @@ -68,6 +102,10 @@ struct Picopass { Widget* widget; DictAttack* dict_attack; Loclass* loclass; + + PicopassDictAttackContext dict_attack_ctx; + PicopassWriteKeyContext write_key_context; + PicopassLoclassContext loclass_context; }; typedef enum { diff --git a/picopass/picopass_worker.c b/picopass/picopass_worker.c deleted file mode 100644 index 87fea7c7..00000000 --- a/picopass/picopass_worker.c +++ /dev/null @@ -1,1336 +0,0 @@ -#include "picopass_worker_i.h" - -#include -#include - -#define TAG "PicopassWorker" - -#define HAS_MASK(x, b) ((x & b) == b) - -// CSNs from Proxmark3 repo -static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = { - {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0}, - {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0}, - {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0}, - {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0}, - {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0}, - {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0}, - {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0}, - {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0}, - {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0}, -}; - -static void picopass_worker_enable_field() { - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_ll_poll(); -} - -static ReturnCode picopass_worker_disable_field(ReturnCode rc) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - return rc; -} - -/***************************** Picopass Worker API *******************************/ - -PicopassWorker* picopass_worker_alloc() { - PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker)); - - // Worker thread attributes - picopass_worker->thread = - furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker); - - picopass_worker->callback = NULL; - picopass_worker->context = NULL; - picopass_worker->storage = furi_record_open(RECORD_STORAGE); - - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); - - return picopass_worker; -} - -void picopass_worker_free(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - - furi_thread_free(picopass_worker->thread); - - furi_record_close(RECORD_STORAGE); - - free(picopass_worker); -} - -PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) { - return picopass_worker->state; -} - -void picopass_worker_start( - PicopassWorker* picopass_worker, - PicopassWorkerState state, - PicopassDeviceData* dev_data, - PicopassWorkerCallback callback, - void* context) { - furi_assert(picopass_worker); - furi_assert(dev_data); - - picopass_worker->callback = callback; - picopass_worker->context = context; - picopass_worker->dev_data = dev_data; - picopass_worker_change_state(picopass_worker, state); - furi_thread_start(picopass_worker->thread); -} - -void picopass_worker_stop(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - furi_assert(picopass_worker->thread); - - if(furi_thread_get_state(picopass_worker->thread) == FuriThreadStateStopped) { - return; - } - - if(picopass_worker->state == PicopassWorkerStateBroken || - picopass_worker->state == PicopassWorkerStateReady) { - return; - } - - if(picopass_worker->state != PicopassWorkerStateEmulate && - picopass_worker->state != PicopassWorkerStateLoclass) { - // Can't do this while emulating in transparent mode as SPI isn't active - picopass_worker_disable_field(ERR_NONE); - } - - if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) { - picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); - furi_thread_join(picopass_worker->thread); - } -} - -void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { - picopass_worker->state = state; -} - -/***************************** Picopass Worker Thread *******************************/ - -ReturnCode picopass_detect_card(int timeout) { - UNUSED(timeout); - - ReturnCode err; - - err = rfalPicoPassPollerInitialize(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err); - return err; - } - - err = rfalFieldOnAndStartGT(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err); - return err; - } - - err = rfalPicoPassPollerCheckPresence(); - if(err != ERR_RF_COLLISION) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err); - return err; - } - - return ERR_NONE; -} - -ReturnCode picopass_read_preauth(PicopassBlock* AA1) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - - ReturnCode err; - - 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; - } - - memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, selRes.CSN, sizeof(selRes.CSN)); - FURI_LOG_D( - TAG, - "csn %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_CSN_BLOCK_INDEX].data[0], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[1], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[2], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[3], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[4], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[5], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[6], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]); - - rfalPicoPassReadBlockRes cfg = {0}; - rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); - memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data)); - FURI_LOG_D( - TAG, - "config %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]); - - rfalPicoPassReadBlockRes aia; - rfalPicoPassPollerReadBlock(PICOPASS_SECURE_AIA_BLOCK_INDEX, &aia); - memcpy(AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data)); - FURI_LOG_D( - TAG, - "aia %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6], - AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]); - - return ERR_NONE; -} - -static ReturnCode - picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) { - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - bool elite = (dict_type != IclassStandardDictTypeFlipper); - - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - - uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; - uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data; - - ReturnCode err = ERR_PARAM; - - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - size_t index = 0; - uint8_t key[PICOPASS_BLOCK_LEN] = {0}; - - if(!iclass_elite_dict_check_presence(dict_type)) { - FURI_LOG_E(TAG, "Dictionary not found"); - return ERR_PARAM; - } - - IclassEliteDict* dict = iclass_elite_dict_alloc(dict_type); - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not allocated"); - return ERR_PARAM; - } - - FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict)); - while(iclass_elite_dict_get_next_key(dict, key)) { - FURI_LOG_D( - TAG, - "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", - elite ? "elite" : "standard", - index++, - key[0], - key[1], - key[2], - key[3], - key[4], - key[5], - key[6], - key[7]); - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - break; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - loclass_iclass_calc_div_key(csn, key, div_key, elite); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err == ERR_NONE) { - memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); - break; - } - - if(picopass_worker->state != PicopassWorkerStateDetect) break; - } - - iclass_elite_dict_free(dict); - - return err; -} - -ReturnCode picopass_auth(PicopassWorker* picopass_worker) { - ReturnCode err; - - FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); - err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper); - if(err == ERR_NONE) { - return ERR_NONE; - } - - /* Because size of the user dictionary and could introduce confusing delay - * to the read screen (since there is no feedback), we omit checking it. - * It will be checked when the user uses Elite Dict. Attack, which has a progress bar - */ - - FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); - err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper); - if(err == ERR_NONE) { - return ERR_NONE; - } - - return err; -} - -ReturnCode picopass_read_card(PicopassBlock* AA1) { - ReturnCode err; - - size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ? - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : - PICOPASS_MAX_APP_LIMIT; - - for(size_t i = 2; i < app_limit; i++) { - if(i == PICOPASS_SECURE_KD_BLOCK_INDEX) { - // Skip over Kd block which is populated earlier (READ of Kd returns all FF's) - continue; - } - - rfalPicoPassReadBlockRes block; - err = rfalPicoPassPollerReadBlock(i, &block); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); - return err; - } - - FURI_LOG_D( - TAG, - "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x", - i, - block.data[0], - block.data[1], - block.data[2], - block.data[3], - block.data[4], - block.data[5], - block.data[6], - block.data[7]); - - memcpy(AA1[i].data, block.data, sizeof(block.data)); - } - - return ERR_NONE; -} - -ReturnCode picopass_write_card(PicopassBlock* AA1) { - 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_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false); - 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; - } - - for(size_t i = 6; i < 10; i++) { - FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i); - uint8_t data[9] = {0}; - data[0] = i; - memcpy(data + 1, AA1[i].data, PICOPASS_BLOCK_LEN); - 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", - i, - 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(i, AA1[i].data, mac); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); - return err; - } - } - - return ERR_NONE; -} - -ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - 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 - - if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN) != 0) { - FURI_LOG_E(TAG, "Wrong CSN for write"); - return ERR_REQUEST; - } - - loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 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), AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 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; -} - -void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - furi_assert(picopass_worker->callback); - - picopass_device_data_clear(picopass_worker->dev_data); - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - - for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { - memset(AA1[i].data, 0, sizeof(AA1[i].data)); - } - memset(pacs, 0, sizeof(PicopassPacs)); - - IclassEliteDictAttackData* dict_attack_data = - &picopass_worker->dev_data->iclass_elite_dict_attack_data; - bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper); - - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - size_t index = 0; - uint8_t key[PICOPASS_BLOCK_LEN] = {0}; - - // Load dictionary - IclassEliteDict* dict = dict_attack_data->dict; - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not found"); - picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context); - return; - } - - do { - if(picopass_detect_card(1000) == ERR_NONE) { - picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context); - - // Process first found device - err = picopass_read_preauth(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - return; - } - - // Thank you proxmark! - 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"); - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - return; - } - - break; - } else { - picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context); - } - if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; - - furi_delay_ms(100); - } while(true); - - FURI_LOG_D( - TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict)); - while(iclass_elite_dict_get_next_key(dict, key)) { - FURI_LOG_T(TAG, "Key %zu", index); - if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) { - picopass_worker->callback( - PicopassWorkerEventNewDictKeyBatch, picopass_worker->context); - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - break; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; - uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data; - - loclass_iclass_calc_div_key(csn, key, div_key, elite); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err == ERR_NONE) { - FURI_LOG_I( - TAG, - "Found key: %02x%02x%02x%02x%02x%02x%02x%02x", - key[0], - key[1], - key[2], - key[3], - key[4], - key[5], - key[6], - key[7]); - - memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); - pacs->elite_kdf = elite; - err = picopass_read_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - - err = picopass_device_parse_credential(AA1, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - - err = picopass_device_parse_wiegand(pacs->credential, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - break; - } - - if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; - } - FURI_LOG_D(TAG, "Dictionary complete"); - if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { - picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); - } else { - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - } -} - -int32_t picopass_worker_task(void* context) { - PicopassWorker* picopass_worker = context; - - if(picopass_worker->state == PicopassWorkerStateDetect) { - picopass_worker_enable_field(); - picopass_worker_detect(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateWrite) { - picopass_worker_enable_field(); - picopass_worker_write(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateWriteKey) { - picopass_worker_enable_field(); - picopass_worker_write_key(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { - picopass_worker_enable_field(); - picopass_worker_elite_dict_attack(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateEmulate) { - picopass_worker_emulate(picopass_worker, false); - } else if(picopass_worker->state == PicopassWorkerStateLoclass) { - picopass_worker_emulate(picopass_worker, true); - } else if(picopass_worker->state == PicopassWorkerStateStop) { - FURI_LOG_D(TAG, "Worker state stop"); - // no-op - } else { - FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state); - } - picopass_worker_disable_field(ERR_NONE); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); - - return 0; -} - -void picopass_worker_detect(PicopassWorker* picopass_worker) { - picopass_device_data_clear(picopass_worker->dev_data); - PicopassDeviceData* dev_data = picopass_worker->dev_data; - - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - ReturnCode err; - - // reset device data - for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { - memset(AA1[i].data, 0, sizeof(AA1[i].data)); - } - memset(pacs, 0, sizeof(PicopassPacs)); - - PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; - - while(picopass_worker->state == PicopassWorkerStateDetect) { - if(picopass_detect_card(1000) == ERR_NONE) { - // Process first found device - err = picopass_read_preauth(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); - nextState = PicopassWorkerEventFail; - } - - // Thank you proxmark! - 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"); - nextState = PicopassWorkerEventFail; - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_auth(picopass_worker); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_try_auth error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_read_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_device_parse_credential(AA1, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_device_parse_wiegand(pacs->credential, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_wiegand 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); - } -} - -void picopass_worker_write(PicopassWorker* picopass_worker) { - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - ReturnCode err; - PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; - - while(picopass_worker->state == PicopassWorkerStateWrite) { - if(picopass_detect_card(1000) == ERR_NONE) { - err = picopass_write_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_write_card 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); - } -} - -void picopass_worker_write_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_SECURE_KD_BLOCK_INDEX].data; - - uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; - loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf); - - 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 == PicopassWorkerStateWriteKey) { - if(picopass_detect_card(1000) == ERR_NONE) { - err = picopass_write_block(AA1, PICOPASS_SECURE_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); - } -} - -// from proxmark3 armsrc/iclass.c rotateCSN -static void picopass_anticoll_csn(uint8_t* rotated_csn, const uint8_t* original_csn) { - for(uint8_t i = 0; i < 8; i++) { - rotated_csn[i] = (original_csn[i] >> 3) | (original_csn[(i + 1) % 8] << 5); - } -} - -static void picopass_append_crc(uint8_t* buf, uint16_t size) { - uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, buf, size); - - buf[size] = crc & 0xFF; - buf[size + 1] = crc >> 8; -} - -static inline void picopass_emu_read_blocks( - NfcVData* nfcv_data, - uint8_t* buf, - uint8_t block_num, - uint8_t block_count) { - memcpy( - buf, nfcv_data->data + (block_num * PICOPASS_BLOCK_LEN), block_count * PICOPASS_BLOCK_LEN); -} - -static inline void picopass_emu_write_blocks( - NfcVData* nfcv_data, - const uint8_t* buf, - uint8_t block_num, - uint8_t block_count) { - memcpy( - nfcv_data->data + (block_num * PICOPASS_BLOCK_LEN), buf, block_count * PICOPASS_BLOCK_LEN); -} - -static void picopass_init_cipher_state_key( - NfcVData* nfcv_data, - PicopassEmulatorCtx* ctx, - const uint8_t key[PICOPASS_BLOCK_LEN]) { - uint8_t cc[PICOPASS_BLOCK_LEN]; - picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1); - - ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key); -} - -static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) { - uint8_t key[PICOPASS_BLOCK_LEN]; - - picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1); - - picopass_init_cipher_state_key(nfcv_data, ctx, key); -} - -static void - loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) { - // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN - const uint8_t* csn = - loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS]; - memcpy(nfc_data->uid, csn, PICOPASS_BLOCK_LEN); - picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1); - - uint8_t key[PICOPASS_BLOCK_LEN]; - loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false); - picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1); - - picopass_init_cipher_state_key(nfcv_data, ctx, key); -} - -static void picopass_emu_handle_packet( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - PicopassEmulatorCtx* ctx = nfcv_data->emu_protocol_ctx; - uint8_t response[34]; - uint8_t response_length = 0; - uint8_t key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX; - - const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - - if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) { - return; - } - - switch(nfcv_data->frame[0]) { - case RFAL_PICOPASS_CMD_ACTALL: // No args - if(nfcv_data->frame_length != 1) { - return; - } - - if(ctx->state != PicopassEmulatorStateHalt) { - ctx->state = PicopassEmulatorStateActive; - } - - // Send SOF only - break; - case RFAL_PICOPASS_CMD_ACT: // No args - if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateActive) { - return; - } - - // Send SOF only - break; - case RFAL_PICOPASS_CMD_HALT: // No args - if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateSelected) { - return; - } - - // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead - ctx->state = PicopassEmulatorStateIdle; - - // Send SOF only - break; - case RFAL_PICOPASS_CMD_READ_OR_IDENTIFY: - if(nfcv_data->frame_length == 1 && - ctx->state == PicopassEmulatorStateActive) { // PICOPASS_CMD_IDENTIFY - // ASNB(8) CRC16(2) - picopass_anticoll_csn(response, nfc_data->uid); - picopass_append_crc(response, PICOPASS_BLOCK_LEN); - response_length = PICOPASS_BLOCK_LEN + 2; - break; - } else if( - nfcv_data->frame_length == 4 && - ctx->state == PicopassEmulatorStateSelected) { // PICOPASS_CMD_READ ADDRESS(1) CRC16(2) - if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) { - return; - } - - // TODO: Check CRC? - // TODO: Check auth? - - // DATA(8) CRC16(2) - if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX || - nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) { - // Reading Kd or Kc blocks always returns FF's - memcpy(response, block_ff, PICOPASS_BLOCK_LEN); - } else { - picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1); - } - picopass_append_crc(response, PICOPASS_BLOCK_LEN); - response_length = PICOPASS_BLOCK_LEN + 2; - break; - } - - return; - case RFAL_PICOPASS_CMD_READ4: // ADDRESS(1) CRC16(2) - if(nfcv_data->frame_length != 4 || ctx->state != PicopassEmulatorStateSelected || - nfcv_data->frame[1] + 4 >= PICOPASS_MAX_APP_LIMIT) { - return; - } - - // TODO: Check CRC? - // TODO: Check auth? - - uint8_t blockNum = nfcv_data->frame[1]; - - // DATA(32) CRC16(2) - picopass_emu_read_blocks(nfcv_data, response, blockNum, 4); - if(blockNum == 4) { - // Kc is block 4, so just redact first block of response - memcpy(response, block_ff, PICOPASS_BLOCK_LEN); - } else if(blockNum < 4) { - // Kd is block 3 - uint8_t* kdOffset = response + ((3 - blockNum) * PICOPASS_BLOCK_LEN); - memcpy(kdOffset, block_ff, PICOPASS_BLOCK_LEN); - if(blockNum != 0) { - // Redact Kc - memcpy(kdOffset + PICOPASS_BLOCK_LEN, block_ff, PICOPASS_BLOCK_LEN); - } - } - picopass_append_crc(response, PICOPASS_BLOCK_LEN * 4); - response_length = (PICOPASS_BLOCK_LEN * 4) + 2; - break; - case RFAL_PICOPASS_CMD_SELECT: // ASNB(8)|SERIALNB(8) - if(nfcv_data->frame_length != 9) { - return; - } - - uint8_t select_csn[PICOPASS_BLOCK_LEN]; - if(ctx->state == PicopassEmulatorStateHalt || ctx->state == PicopassEmulatorStateIdle) { - memcpy(select_csn, nfc_data->uid, PICOPASS_BLOCK_LEN); - } else { - picopass_anticoll_csn(select_csn, nfc_data->uid); - } - - if(memcmp(nfcv_data->frame + 1, select_csn, PICOPASS_BLOCK_LEN)) { - if(ctx->state == PicopassEmulatorStateActive) { - ctx->state = PicopassEmulatorStateIdle; - } else if(ctx->state == PicopassEmulatorStateSelected) { - // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead - ctx->state = PicopassEmulatorStateIdle; - } - - return; - } - - ctx->state = PicopassEmulatorStateSelected; - - // SERIALNB(8) CRC16(2) - memcpy(response, nfc_data->uid, PICOPASS_BLOCK_LEN); - picopass_append_crc(response, PICOPASS_BLOCK_LEN); - - response_length = PICOPASS_BLOCK_LEN + 2; - break; - case RFAL_PICOPASS_CMD_READCHECK_KC: // ADDRESS(1) - key_block_num = PICOPASS_SECURE_KC_BLOCK_INDEX; - // fallthrough - case RFAL_PICOPASS_CMD_READCHECK_KD: // ADDRESS(1) - if(nfcv_data->frame_length != 2 || - nfcv_data->frame[1] != PICOPASS_SECURE_EPURSE_BLOCK_INDEX || - ctx->state != PicopassEmulatorStateSelected) { - return; - } - - // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode - // we can also no-op if the key block is the same, CHECK re-inits if it failed already - if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) { - ctx->key_block_num = key_block_num; - picopass_init_cipher_state(nfcv_data, ctx); - } - - // DATA(8) - picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1); - response_length = PICOPASS_BLOCK_LEN; - break; - case RFAL_PICOPASS_CMD_CHECK: // CHALLENGE(4) READERSIGNATURE(4) - if(nfcv_data->frame_length != 9 || ctx->state != PicopassEmulatorStateSelected) { - return; - } - - if(ctx->loclass_mode) { - // LOCLASS Reader attack mode - - // Copy EPURSE - uint8_t cc[PICOPASS_BLOCK_LEN]; - picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1); - -#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY - uint8_t key[PICOPASS_BLOCK_LEN]; - // loclass mode stores the derived standard debit key in Kd to check - picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1); - - uint8_t rmac[4]; - loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key); - - if(!memcmp(nfcv_data->frame + 5, rmac, 4)) { - // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader. - // Either way no point logging it. - - FURI_LOG_W(TAG, "loclass: standard key detected during collection"); - ctx->loclass_got_std_key = true; - - // Don't reset the state as the reader may try a different key next without going through anticoll - // The reader is always free to redo the anticoll if it wants to anyway - - return; - } -#endif - - // Save to buffer to defer flushing when we rotate CSN - memcpy( - ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8), - nfcv_data->frame + 1, - 8); - - // Rotate to the next CSN/attempt - ctx->key_block_num++; - - // CSN changed - if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) { - // Flush NR-MACs for this CSN to SD card - uint8_t cc[PICOPASS_BLOCK_LEN]; - picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1); - - for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) { - loclass_writer_write_params( - ctx->loclass_writer, - ctx->key_block_num + i - LOCLASS_NUM_PER_CSN, - nfc_data->uid, - cc, - ctx->loclass_mac_buffer + (i * 8), - ctx->loclass_mac_buffer + (i * 8) + 4); - } - - if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) { - loclass_update_csn(nfc_data, nfcv_data, ctx); - // Only reset the state when we change to a new CSN for the same reason as when we get a standard key - ctx->state = PicopassEmulatorStateIdle; - } else { - ctx->state = PicopassEmulatorStateStopEmulation; - } - } - - return; - } - - uint8_t key[PICOPASS_BLOCK_LEN]; - picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1); - - uint8_t rmac[4]; - loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key); - -#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC - if(memcmp(nfcv_data->frame + 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_init_cipher_state(nfcv_data, ctx); - return; - } -#endif - - // CHIPRESPONSE(4) - response_length = 4; - break; - case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2) - if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) || - ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) { - return; - } - - if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) { - return; - } - - uint8_t cfgBlock[PICOPASS_BLOCK_LEN]; - picopass_emu_read_blocks(nfcv_data, cfgBlock, PICOPASS_CONFIG_BLOCK_INDEX, 1); - bool persMode = HAS_MASK(cfgBlock[7], PICOPASS_FUSE_PERS); - - if((nfcv_data->frame[1] == PICOPASS_CSN_BLOCK_INDEX) // CSN is always read only - || - (!persMode && - !HAS_MASK(cfgBlock[3], 0x80)) // Chip is in RO mode, no updated possible (even ePurse) - || (!persMode && - nfcv_data->frame[1] == - PICOPASS_SECURE_AIA_BLOCK_INDEX) // AIA can only be set in personalisation mode - || (!persMode && - (nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX || - nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) && - (!HAS_MASK(cfgBlock[7], PICOPASS_FUSE_CRYPT10)))) { - return; // TODO: Is this the right response? - } - - if(nfcv_data->frame[1] >= 6 && nfcv_data->frame[1] <= 12) { - if(!HAS_MASK( - cfgBlock[3], - 1 << (nfcv_data->frame[1] - 6))) { // bit0 is block6, up to bit6 being block12 - // Block is marked as read-only, deny writing - return; // TODO: Is this the right response? - } - } - - // TODO: Check CRC/SIGN depending on if in secure mode - // Check correct key - // -> Kd only allows decrementing e-Purse - // -> per-app controlled by key access config - //bool keyAccess = HAS_MASK(cfgBlock[5], 0x01); - // -> must auth with that key to change it - - uint8_t blockOffset = nfcv_data->frame[1]; - uint8_t block[PICOPASS_BLOCK_LEN]; - switch(nfcv_data->frame[1]) { - case PICOPASS_CONFIG_BLOCK_INDEX: - block[0] = cfgBlock[0]; // Applications Limit - block[1] = cfgBlock[1] & nfcv_data->frame[3]; // OTP - block[2] = cfgBlock[2] & nfcv_data->frame[4]; // OTP - block[3] = cfgBlock[3] & nfcv_data->frame[5]; // Block Write Lock - block[4] = cfgBlock[4]; // Chip Config - block[5] = cfgBlock[5]; // Memory Config - block[6] = nfcv_data->frame[8]; // EAS - block[7] = cfgBlock[7]; // Fuses - - // Some parts allow w (but not e) if in persMode - if(persMode) { - block[0] &= nfcv_data->frame[2]; // Applications Limit - block[4] &= nfcv_data->frame[6]; // Chip Config - block[5] &= nfcv_data->frame[7]; // Memory Config - block[7] &= nfcv_data->frame[9]; // Fuses - } else { - // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode - block[7] &= nfcv_data->frame[9] | ~PICOPASS_FUSE_CRYPT10; - } - break; - case PICOPASS_SECURE_EPURSE_BLOCK_INDEX: - // ePurse updates swap first and second half of the block each update - memcpy(block + 4, nfcv_data->frame + 2, 4); - memcpy(block, nfcv_data->frame + 6, 4); - break; - case PICOPASS_SECURE_KD_BLOCK_INDEX: - // fallthrough - case PICOPASS_SECURE_KC_BLOCK_INDEX: - if(!persMode) { - picopass_emu_read_blocks(nfcv_data, block, blockOffset, 1); - for(uint8_t i = 0; i < sizeof(PICOPASS_BLOCK_LEN); i++) - block[i] ^= nfcv_data->frame[i + 2]; - break; - } - // Use default case when in personalisation mode - // fallthrough - default: - memcpy(block, nfcv_data->frame + 2, PICOPASS_BLOCK_LEN); - break; - } - - picopass_emu_write_blocks(nfcv_data, block, blockOffset, 1); - - if((nfcv_data->frame[1] == ctx->key_block_num || - nfcv_data->frame[1] == PICOPASS_SECURE_EPURSE_BLOCK_INDEX) && - !ctx->loclass_mode) - picopass_init_cipher_state(nfcv_data, ctx); - - // DATA(8) CRC16(2) - if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX || - nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX) { - // Key updates always return FF's - memcpy(response, block_ff, PICOPASS_BLOCK_LEN); - } else { - memcpy(response, block, PICOPASS_BLOCK_LEN); - } - picopass_append_crc(response, PICOPASS_BLOCK_LEN); - response_length = PICOPASS_BLOCK_LEN + 2; - break; - case RFAL_PICOPASS_CMD_PAGESEL: // PAGE(1) CRC16(2) - // Chips with a single page do not answer to this command - // BLOCK1(8) CRC16(2) - return; - case RFAL_PICOPASS_CMD_DETECT: - // TODO - not used by iClass though - return; - default: - return; - } - - NfcVSendFlags flags = NfcVSendFlagsSof | NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; - if(response_length > 0) { - flags |= NfcVSendFlagsEof; - } - - nfcv_emu_send( - tx_rx, - nfcv_data, - response, - response_length, - flags, - nfcv_data->eof_timestamp + NFCV_FDT_FC(4000)); // 3650 is ~254uS 4000 is ~283uS -} - -void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode) { - furi_hal_nfc_exit_sleep(); - - FuriHalNfcTxRxContext tx_rx = {}; - PicopassEmulatorCtx emu_ctx = { - .state = PicopassEmulatorStateIdle, - .key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX, - .loclass_mode = loclass_mode, - .loclass_got_std_key = false, - .loclass_writer = NULL, - }; - FuriHalNfcDevData nfc_data = { - .uid_len = RFAL_PICOPASS_UID_LEN, - }; - NfcVData* nfcv_data = malloc(sizeof(NfcVData)); - nfcv_data->block_size = PICOPASS_BLOCK_LEN; - nfcv_data->emu_protocol_ctx = &emu_ctx; - nfcv_data->emu_protocol_handler = &picopass_emu_handle_packet; - - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* blocks = dev_data->AA1; - - if(loclass_mode) { - emu_ctx.loclass_writer = loclass_writer_alloc(); - if(emu_ctx.loclass_writer == NULL) { - picopass_worker->callback( - PicopassWorkerEventLoclassFileError, picopass_worker->context); - - while(picopass_worker->state == PicopassWorkerStateEmulate || - picopass_worker->state == PicopassWorkerStateLoclass) { - furi_delay_ms(1); - } - - free(nfcv_data); - - return; - } - - // Setup blocks for loclass attack - uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C}; - picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1); - - uint8_t epurse[8] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - picopass_emu_write_blocks(nfcv_data, epurse, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1); - - uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1); - - emu_ctx.key_block_num = 0; - loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx); - - loclass_writer_write_start_stop(emu_ctx.loclass_writer, true); - } else { - memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); - memcpy(nfcv_data->data, blocks, sizeof(dev_data->AA1)); - picopass_init_cipher_state(nfcv_data, &emu_ctx); - } - - uint8_t last_loclass_csn_num = 0; - bool loclass_got_std_key = false; - - nfcv_emu_init(&nfc_data, nfcv_data); - while(picopass_worker->state == PicopassWorkerStateEmulate || - picopass_worker->state == PicopassWorkerStateLoclass) { - if(nfcv_emu_loop(&tx_rx, &nfc_data, nfcv_data, 500)) { - if(picopass_worker->callback) { - if((loclass_mode) && (last_loclass_csn_num != emu_ctx.key_block_num)) { - last_loclass_csn_num = emu_ctx.key_block_num; - picopass_worker->callback( - PicopassWorkerEventLoclassGotMac, picopass_worker->context); - } else if((loclass_mode) && !loclass_got_std_key && emu_ctx.loclass_got_std_key) { - loclass_got_std_key = true; - picopass_worker->callback( - PicopassWorkerEventLoclassGotStandardKey, picopass_worker->context); - } else { - picopass_worker->callback( - PicopassWorkerEventSuccess, picopass_worker->context); - } - } - } - furi_delay_us(1); - } - - if(emu_ctx.loclass_writer) { - loclass_writer_write_start_stop(emu_ctx.loclass_writer, false); - loclass_writer_free(emu_ctx.loclass_writer); - } - - nfcv_emu_deinit(nfcv_data); - free(nfcv_data); -} diff --git a/picopass/picopass_worker.h b/picopass/picopass_worker.h deleted file mode 100644 index ad1f5a1d..00000000 --- a/picopass/picopass_worker.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "picopass_device.h" -#include "picopass_keys.h" - -typedef struct PicopassWorker PicopassWorker; - -typedef enum { - // Init states - PicopassWorkerStateNone, - PicopassWorkerStateBroken, - PicopassWorkerStateReady, - // Main worker states - PicopassWorkerStateDetect, - PicopassWorkerStateWrite, - PicopassWorkerStateWriteKey, - PicopassWorkerStateEliteDictAttack, - PicopassWorkerStateEmulate, - PicopassWorkerStateLoclass, - // Transition - PicopassWorkerStateStop, -} PicopassWorkerState; - -typedef enum { - // Reserve first 50 events for application events - PicopassWorkerEventReserved = 50, - - // Picopass worker common events - PicopassWorkerEventSuccess, - PicopassWorkerEventFail, - PicopassWorkerEventNoCardDetected, - PicopassWorkerEventSeEnabled, - PicopassWorkerEventAborted, - PicopassWorkerEventCardDetected, - PicopassWorkerEventNewDictKeyBatch, - PicopassWorkerEventNoDictFound, - PicopassWorkerEventLoclassGotMac, - PicopassWorkerEventLoclassGotStandardKey, - PicopassWorkerEventLoclassFileError, -} PicopassWorkerEvent; - -typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); - -PicopassWorker* picopass_worker_alloc(); - -PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker); - -void picopass_worker_free(PicopassWorker* picopass_worker); - -void picopass_worker_start( - PicopassWorker* picopass_worker, - PicopassWorkerState state, - PicopassDeviceData* dev_data, - PicopassWorkerCallback callback, - void* context); - -void picopass_worker_stop(PicopassWorker* picopass_worker); diff --git a/picopass/picopass_worker_i.h b/picopass/picopass_worker_i.h deleted file mode 100644 index 5e51b1cc..00000000 --- a/picopass/picopass_worker_i.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "picopass_worker.h" -#include "loclass_writer.h" -#include "picopass_i.h" - -#include -#include - -#include - -#include -#include - -#include - -struct PicopassWorker { - FuriThread* thread; - Storage* storage; - Stream* dict_stream; - - PicopassDeviceData* dev_data; - PicopassWorkerCallback callback; - void* context; - - PicopassWorkerState state; -}; - -void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state); - -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_key(PicopassWorker* picopass_worker); -void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode); diff --git a/picopass/protocol/picopass_listener.c b/picopass/protocol/picopass_listener.c new file mode 100644 index 00000000..f5cef443 --- /dev/null +++ b/picopass/protocol/picopass_listener.c @@ -0,0 +1,667 @@ +#include "picopass_listener_i.h" +#include "picopass_keys.h" + +#include + +#define PICOPASS_LISTENER_HAS_MASK(x, b) ((x & b) == b) + +typedef enum { + PicopassListenerCommandProcessed, + PicopassListenerCommandSilent, + PicopassListenerCommandSendSoF, + PicopassListenerCommandStop, +} PicopassListenerCommand; + +typedef PicopassListenerCommand ( + *PicopassListenerCommandHandler)(PicopassListener* instance, BitBuffer* buf); + +typedef struct { + uint8_t start_byte_cmd; + size_t cmd_len_bits; + PicopassListenerCommandHandler handler; +} PicopassListenerCmd; + +// CSNs from Proxmark3 repo +static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = { + {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0}, + {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0}, + {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0}, + {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0}, + {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0}, + {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0}, + {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0}, + {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0}, + {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0}, +}; + +static void picopass_listener_reset(PicopassListener* instance) { + instance->state = PicopassListenerStateIdle; +} + +static void picopass_listener_loclass_update_csn(PicopassListener* instance) { + // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN + const uint8_t* csn = + loclass_csns[(instance->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS]; + memcpy(instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn, sizeof(PicopassBlock)); + + uint8_t key[PICOPASS_BLOCK_LEN] = {}; + loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false); + memcpy(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, key, sizeof(PicopassBlock)); + + picopass_listener_init_cipher_state_key(instance, key); +} + +PicopassListenerCommand + picopass_listener_actall_handler(PicopassListener* instance, BitBuffer* buf) { + UNUSED(buf); + + if(instance->state != PicopassListenerStateHalt) { + instance->state = PicopassListenerStateActive; + } + // nfc_set_fdt_listen_fc(instance->nfc, 1000); + + return PicopassListenerCommandSendSoF; +} + +PicopassListenerCommand picopass_listener_act_handler(PicopassListener* instance, BitBuffer* buf) { + UNUSED(buf); + + PicopassListenerCommand command = PicopassListenerCommandSendSoF; + + if(instance->state != PicopassListenerStateActive) { + command = PicopassListenerCommandSilent; + } + + return command; +} + +PicopassListenerCommand + picopass_listener_halt_handler(PicopassListener* instance, BitBuffer* buf) { + UNUSED(buf); + + PicopassListenerCommand command = PicopassListenerCommandSendSoF; + + // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead + instance->state = PicopassListenerStateIdle; + + return command; +} + +PicopassListenerCommand + picopass_listener_identify_handler(PicopassListener* instance, BitBuffer* buf) { + UNUSED(buf); + + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if(instance->state != PicopassListenerStateActive) break; + picopass_listener_write_anticoll_csn(instance, instance->tx_buffer); + PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer); + if(error != PicopassErrorNone) { + FURI_LOG_D(TAG, "Error sending CSN: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_select_handler(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if((instance->state == PicopassListenerStateHalt) || + (instance->state == PicopassListenerStateIdle)) { + bit_buffer_copy_bytes( + instance->tmp_buffer, + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, + sizeof(PicopassBlock)); + } else { + picopass_listener_write_anticoll_csn(instance, instance->tmp_buffer); + } + const uint8_t* listener_uid = bit_buffer_get_data(instance->tmp_buffer); + const uint8_t* received_data = bit_buffer_get_data(buf); + + if(memcmp(listener_uid, &received_data[1], PICOPASS_BLOCK_LEN) != 0) { + if(instance->state == PicopassListenerStateActive) { + instance->state = PicopassListenerStateIdle; + } else if(instance->state == PicopassListenerStateSelected) { + // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead + instance->state = PicopassListenerStateIdle; + } + break; + } + + instance->state = PicopassListenerStateSelected; + bit_buffer_copy_bytes( + instance->tx_buffer, + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, + sizeof(PicopassBlock)); + + PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer); + if(error != PicopassErrorNone) { + FURI_LOG_D(TAG, "Error sending select response: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_read_handler(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + uint8_t block_num = bit_buffer_get_byte(buf, 1); + if(block_num > PICOPASS_MAX_APP_LIMIT) break; + + bit_buffer_reset(instance->tx_buffer); + if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) || + (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + bit_buffer_append_byte(instance->tx_buffer, 0xff); + } + } else { + bit_buffer_copy_bytes( + instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock)); + } + PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer); + if(error != PicopassErrorNone) { + FURI_LOG_D(TAG, "Failed to tx read block response: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +static PicopassListenerCommand + picopass_listener_readcheck(PicopassListener* instance, BitBuffer* buf, uint8_t key_block_num) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if(instance->state != PicopassListenerStateSelected) break; + uint8_t block_num = bit_buffer_get_byte(buf, 1); + if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break; + + // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode + // we can also no-op if the key block is the same, CHECK re-inits if it failed already + if((instance->key_block_num != key_block_num) && + (instance->mode != PicopassListenerModeLoclass)) { + instance->key_block_num = key_block_num; + picopass_listener_init_cipher_state(instance); + } + + // DATA(8) + bit_buffer_copy_bytes( + instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock)); + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_D(TAG, "Failed to tx read check response: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_readcheck_kd_handler(PicopassListener* instance, BitBuffer* buf) { + return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KD_BLOCK_INDEX); +} + +PicopassListenerCommand + picopass_listener_readcheck_kc_handler(PicopassListener* instance, BitBuffer* buf) { + return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KC_BLOCK_INDEX); +} + +PicopassListenerCommand + picopass_listener_check_handler_loclass(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + NfcCommand callback_command = NfcCommandContinue; + + // LOCLASS Reader attack mode + do { +#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY + // loclass mode stores the derived standard debit key in Kd to check + + PicopassBlock key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX]; + uint8_t rmac[4]; + uint8_t rx_data[9] = {}; + bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data)); + loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data); + + if(!memcmp(&rx_data[5], rmac, 4)) { + // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader. + // Either way no point logging it. + + FURI_LOG_W(TAG, "loclass: standard key detected during collection"); + if(instance->callback) { + instance->event.type = PicopassListenerEventTypeLoclassGotStandardKey; + callback_command = instance->callback(instance->event, instance->context); + if(callback_command == NfcCommandStop) { + command = PicopassListenerCommandStop; + } + } + + // Don't reset the state as the reader may try a different key next without going through anticoll + // The reader is always free to redo the anticoll if it wants to anyway + + break; + } +#endif + + // Save to buffer to defer flushing when we rotate CSN + memcpy( + instance->loclass_mac_buffer + ((instance->key_block_num % LOCLASS_NUM_PER_CSN) * 8), + &rx_data[1], + 8); + + // Rotate to the next CSN/attempt + instance->key_block_num++; + + // CSN changed + if(instance->key_block_num % LOCLASS_NUM_PER_CSN == 0) { + // Flush NR-MACs for this CSN to SD card + for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) { + loclass_writer_write_params( + instance->writer, + instance->key_block_num + i - LOCLASS_NUM_PER_CSN, + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, + instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, + instance->loclass_mac_buffer + (i * 8), + instance->loclass_mac_buffer + (i * 8) + 4); + } + + if(instance->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) { + picopass_listener_loclass_update_csn(instance); + // Only reset the state when we change to a new CSN for the same reason as when we get a standard key + instance->state = PicopassListenerStateIdle; + } + } + if(instance->callback) { + instance->event.type = PicopassListenerEventTypeLoclassGotMac; + instance->callback(instance->event, instance->context); + } + + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + uint8_t rmac[4] = {}; + uint8_t tmac[4] = {}; + const uint8_t* key = instance->data->AA1[instance->key_block_num].data; + // 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); + +#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); + 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; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} +PicopassListenerCommand + picopass_listener_check_handler(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if(instance->state != PicopassListenerStateSelected) break; + if(instance->mode == PicopassListenerModeLoclass) { + command = picopass_listener_check_handler_loclass(instance, buf); + } else { + command = picopass_listener_check_handler_emulation(instance, buf); + } + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_update_handler(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if(instance->mode == PicopassListenerModeLoclass) break; + if(instance->state != PicopassListenerStateSelected) break; + + PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX]; + bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS); + + const uint8_t* rx_data = bit_buffer_get_data(buf); + uint8_t block_num = rx_data[1]; + if(block_num == PICOPASS_CSN_BLOCK_INDEX) break; // CSN is always read only + if(!pers_mode && PICOPASS_LISTENER_HAS_MASK(config_block.data[3], 0x80)) + break; // Chip is in RO mode, no updated possible (even ePurse) + if(!pers_mode && (block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX)) + break; // AIA can only be set in personalisation mode + if(!pers_mode && + ((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX || + block_num == PICOPASS_SECURE_KC_BLOCK_INDEX) && + (!PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_CRYPT10)))) + break; + + if(block_num >= 6 && block_num <= 12) { + // bit0 is block6, up to bit6 being block12 + if(!PICOPASS_LISTENER_HAS_MASK(config_block.data[3], (1 << (block_num - 6)))) { + // Block is marked as read-only, deny writing + break; + } + } + // TODO: Check CRC/SIGN depending on if in secure mode + // Check correct key + // -> Kd only allows decrementing e-Purse + // -> per-app controlled by key access config + // bool keyAccess = PICOPASS_LISTENER_HAS_MASK(config_block.data[5], 0x01); + // -> must auth with that key to change it + + PicopassBlock new_block = {}; + switch(block_num) { + case PICOPASS_CONFIG_BLOCK_INDEX: + new_block.data[0] = config_block.data[0]; // Applications Limit + new_block.data[1] = config_block.data[1] & rx_data[3]; // OTP + new_block.data[2] = config_block.data[2] & rx_data[4]; // OTP + new_block.data[3] = config_block.data[3] & rx_data[5]; // Block Write Lock + new_block.data[4] = config_block.data[4]; // Chip Config + new_block.data[5] = config_block.data[5]; // Memory Config + new_block.data[6] = rx_data[8]; // EAS + new_block.data[7] = config_block.data[7]; // Fuses + + // Some parts allow w (but not e) if in persMode + if(pers_mode) { + new_block.data[0] &= rx_data[2]; // Applications Limit + new_block.data[4] &= rx_data[6]; // Chip Config + new_block.data[5] &= rx_data[7]; // Memory Config + new_block.data[7] &= rx_data[9]; // Fuses + } else { + // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode + new_block.data[7] &= rx_data[9] | ~PICOPASS_FUSE_CRYPT10; + } + break; + + case PICOPASS_SECURE_EPURSE_BLOCK_INDEX: + // ePurse updates swap first and second half of the block each update + memcpy(&new_block.data[4], &rx_data[2], 4); + memcpy(&new_block.data[0], &rx_data[6], 4); + break; + + case PICOPASS_SECURE_KD_BLOCK_INDEX: + // fallthrough + case PICOPASS_SECURE_KC_BLOCK_INDEX: + if(!pers_mode) { + new_block = instance->data->AA1[block_num]; + for(size_t i = 0; i < sizeof(PicopassBlock); i++) { + new_block.data[i] ^= rx_data[i + 2]; + } + break; + } + // Use default case when in personalisation mode + // fallthrough + default: + memcpy(new_block.data, &rx_data[2], sizeof(PicopassBlock)); + break; + } + + instance->data->AA1[block_num] = new_block; + if((block_num == instance->key_block_num) || + (block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) { + picopass_listener_init_cipher_state(instance); + } + + bit_buffer_reset(instance->tx_buffer); + if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) || + (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + // Key updates always return FF's + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + bit_buffer_append_byte(instance->tx_buffer, 0xff); + } + } else { + bit_buffer_copy_bytes( + instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock)); + } + + PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer); + if(error != PicopassErrorNone) { + FURI_LOG_D(TAG, "Failed to tx update response: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +PicopassListenerCommand + picopass_listener_read4_handler(PicopassListener* instance, BitBuffer* buf) { + PicopassListenerCommand command = PicopassListenerCommandSilent; + + do { + if(instance->state != PicopassListenerStateSelected) break; + + uint8_t block_start = bit_buffer_get_byte(buf, 1); + if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break; + + // TODO: Check CRC? + // TODO: Check auth? + + bit_buffer_reset(instance->tx_buffer); + for(uint8_t i = block_start; i < block_start + 4; i++) { + if((i == PICOPASS_SECURE_KD_BLOCK_INDEX) || (i == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + for(size_t j = 0; j < sizeof(PicopassBlock); j++) { + bit_buffer_append_byte(instance->tx_buffer, 0xff); + } + } else { + bit_buffer_append_bytes( + instance->tx_buffer, instance->data->AA1[i].data, sizeof(PicopassBlock)); + } + } + + PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer); + if(error != PicopassErrorNone) { + FURI_LOG_D(TAG, "Failed to tx read4 response: %d", error); + break; + } + + command = PicopassListenerCommandProcessed; + } while(false); + + return command; +} + +static const PicopassListenerCmd picopass_listener_cmd_handlers[] = { + { + .start_byte_cmd = RFAL_PICOPASS_CMD_ACTALL, + .cmd_len_bits = 8, + .handler = picopass_listener_actall_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_ACT, + .cmd_len_bits = 8, + .handler = picopass_listener_act_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_HALT, + .cmd_len_bits = 8, + .handler = picopass_listener_halt_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, + .cmd_len_bits = 8, + .handler = picopass_listener_identify_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_SELECT, + .cmd_len_bits = 8 * 9, + .handler = picopass_listener_select_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, + .cmd_len_bits = 8 * 4, + .handler = picopass_listener_read_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KD, + .cmd_len_bits = 8 * 2, + .handler = picopass_listener_readcheck_kd_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KC, + .cmd_len_bits = 8 * 2, + .handler = picopass_listener_readcheck_kc_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_CHECK, + .cmd_len_bits = 8 * 9, + .handler = picopass_listener_check_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_UPDATE, + .cmd_len_bits = 8 * 14, + .handler = picopass_listener_update_handler, + }, + { + .start_byte_cmd = RFAL_PICOPASS_CMD_READ4, + .cmd_len_bits = 8 * 4, + .handler = picopass_listener_read4_handler, + }, +}; + +PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) { + furi_assert(nfc); + furi_assert(data); + + PicopassListener* instance = malloc(sizeof(PicopassListener)); + instance->nfc = nfc; + instance->data = malloc(sizeof(PicopassDeviceData)); + mempcpy(instance->data, data, sizeof(PicopassDeviceData)); + + instance->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX); + instance->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX); + + nfc_set_fdt_listen_fc(instance->nfc, PICOPASS_FDT_LISTEN_FC); + nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693); + + return instance; +} + +void picopass_listener_free(PicopassListener* instance) { + furi_assert(instance); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->tmp_buffer); + free(instance->data); + if(instance->writer) { + loclass_writer_write_start_stop(instance->writer, false); + loclass_writer_free(instance->writer); + } + free(instance); +} + +bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode) { + furi_assert(instance); + bool success = true; + + instance->mode = mode; + if(instance->mode == PicopassListenerModeLoclass) { + instance->key_block_num = 0; + picopass_listener_loclass_update_csn(instance); + instance->writer = loclass_writer_alloc(); + if(instance->writer) { + loclass_writer_write_start_stop(instance->writer, true); + } else { + success = false; + } + } + + return success; +} + +NfcCommand picopass_listener_start_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcCommand command = NfcCommandContinue; + PicopassListener* instance = context; + BitBuffer* rx_buf = event.data.buffer; + + PicopassListenerCommand picopass_cmd = PicopassListenerCommandSilent; + if(event.type == NfcEventTypeRxEnd) { + for(size_t i = 0; i < COUNT_OF(picopass_listener_cmd_handlers); i++) { + if(bit_buffer_get_size(rx_buf) != picopass_listener_cmd_handlers[i].cmd_len_bits) { + continue; + } + if(bit_buffer_get_byte(rx_buf, 0) != + picopass_listener_cmd_handlers[i].start_byte_cmd) { + continue; + } + picopass_cmd = picopass_listener_cmd_handlers[i].handler(instance, rx_buf); + break; + } + if(picopass_cmd == PicopassListenerCommandSendSoF) { + nfc_iso15693_listener_tx_sof(instance->nfc); + } else if(picopass_cmd == PicopassListenerCommandStop) { + command = NfcCommandStop; + } + } + + return command; +} + +void picopass_listener_start( + PicopassListener* instance, + PicopassListenerCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + + picopass_listener_reset(instance); + nfc_start(instance->nfc, picopass_listener_start_callback, instance); +} + +void picopass_listener_stop(PicopassListener* instance) { + furi_assert(instance); + + nfc_stop(instance->nfc); +} + +const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance) { + furi_assert(instance); + + return instance->data; +} diff --git a/picopass/protocol/picopass_listener.h b/picopass/protocol/picopass_listener.h new file mode 100644 index 00000000..1a4d64fe --- /dev/null +++ b/picopass/protocol/picopass_listener.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include "picopass_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PicopassListenerModeEmulation, + PicopassListenerModeLoclass, +} PicopassListenerMode; + +typedef enum { + PicopassListenerEventTypeLoclassGotStandardKey, + PicopassListenerEventTypeLoclassGotMac, +} PicopassListenerEventType; + +typedef struct { + PicopassListenerEventType type; +} PicopassListenerEvent; + +typedef NfcCommand (*PicopassListenerCallback)(PicopassListenerEvent event, void* context); + +typedef struct PicopassListener PicopassListener; + +PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data); + +void picopass_listener_free(PicopassListener* instance); + +bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode); + +void picopass_listener_start( + PicopassListener* instance, + PicopassListenerCallback callback, + void* context); + +void picopass_listener_stop(PicopassListener* instance); + +const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance); + +#ifdef __cplusplus +} +#endif diff --git a/picopass/protocol/picopass_listener_i.c b/picopass/protocol/picopass_listener_i.c new file mode 100644 index 00000000..a004c2eb --- /dev/null +++ b/picopass/protocol/picopass_listener_i.c @@ -0,0 +1,52 @@ +#include "picopass_listener_i.h" + +#include + +static PicopassError picopass_listener_process_error(NfcError error) { + PicopassError ret = PicopassErrorNone; + + switch(error) { + case NfcErrorNone: + ret = PicopassErrorNone; + break; + + default: + ret = PicopassErrorTimeout; + break; + } + + return ret; +} + +void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key) { + uint8_t cc[PICOPASS_BLOCK_LEN] = {}; + memcpy( + cc, instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, sizeof(PicopassBlock)); + + instance->cipher_state = loclass_opt_doTagMAC_1(cc, key); +} + +void picopass_listener_init_cipher_state(PicopassListener* instance) { + uint8_t key[PICOPASS_BLOCK_LEN] = {}; + memcpy(key, instance->data->AA1[instance->key_block_num].data, sizeof(PicopassBlock)); + + picopass_listener_init_cipher_state_key(instance, key); +} + +PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer) { + iso13239_crc_append(Iso13239CrcTypePicopass, tx_buffer); + NfcError error = nfc_listener_tx(instance->nfc, tx_buffer); + + return picopass_listener_process_error(error); +} + +// from proxmark3 armsrc/iclass.c rotateCSN +PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer) { + const uint8_t* uid = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data; + bit_buffer_reset(buffer); + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5)); + } + + return PicopassErrorNone; +} diff --git a/picopass/protocol/picopass_listener_i.h b/picopass/protocol/picopass_listener_i.h new file mode 100644 index 00000000..7bd35ffd --- /dev/null +++ b/picopass/protocol/picopass_listener_i.h @@ -0,0 +1,47 @@ +#pragma once + +#include "picopass_listener.h" +#include + +#include +#include +#include + +#define TAG "PicopassListener" + +#define PICOPASS_LISTENER_BUFFER_SIZE_MAX (255) + +typedef enum { + PicopassListenerStateIdle, + PicopassListenerStateHalt, + PicopassListenerStateActive, + PicopassListenerStateSelected, +} PicopassListenerState; + +struct PicopassListener { + Nfc* nfc; + PicopassDeviceData* data; + PicopassListenerState state; + + LoclassState_t cipher_state; + PicopassListenerMode mode; + + BitBuffer* tx_buffer; + BitBuffer* tmp_buffer; + uint8_t key_block_num; + + LoclassWriter* writer; + uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN]; + + PicopassListenerEvent event; + PicopassListenerCallback callback; + void* context; +}; + +void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key); + +void picopass_listener_init_cipher_state(PicopassListener* instance); + +PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer); + +PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer); diff --git a/picopass/protocol/picopass_poller.c b/picopass/protocol/picopass_poller.c new file mode 100644 index 00000000..690dddaa --- /dev/null +++ b/picopass/protocol/picopass_poller.c @@ -0,0 +1,543 @@ +#include "picopass_poller_i.h" + +#include "../loclass/optimized_cipher.h" + +#include + +#define TAG "Picopass" + +typedef NfcCommand (*PicopassPollerStateHandler)(PicopassPoller* instance); + +static void picopass_poller_reset(PicopassPoller* instance) { + instance->current_block = 0; +} + +static void picopass_poller_prepare_read(PicopassPoller* instance) { + instance->app_limit = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < + PICOPASS_MAX_APP_LIMIT ? + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : + PICOPASS_MAX_APP_LIMIT; + instance->current_block = 2; +} + +NfcCommand picopass_poller_request_mode_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->event.type = PicopassPollerEventTypeRequestMode; + command = instance->callback(instance->event, instance->context); + instance->mode = instance->event_data.req_mode.mode; + instance->state = PicopassPollerStateDetect; + + return command; +} + +NfcCommand picopass_poller_detect_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + PicopassError error = picopass_poller_actall(instance); + + if(error == PicopassErrorNone) { + instance->state = PicopassPollerStateSelect; + instance->event.type = PicopassPollerEventTypeCardDetected; + command = instance->callback(instance->event, instance->context); + } else { + furi_delay_ms(100); + } + + return command; +} + +NfcCommand picopass_poller_select_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + PicopassError error = picopass_poller_identify(instance, &instance->col_res_serial_num); + if(error != PicopassErrorNone) { + instance->state = PicopassPollerStateFail; + break; + } + + error = + picopass_poller_select(instance, &instance->col_res_serial_num, &instance->serial_num); + if(error != PicopassErrorNone) { + instance->state = PicopassPollerStateFail; + break; + } + + if(instance->mode == PicopassPollerModeRead) { + instance->state = PicopassPollerStatePreAuth; + } else { + instance->state = PicopassPollerStateAuth; + } + } while(false); + + return command; +} + +NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + PicopassError error = PicopassErrorNone; + + do { + memcpy( + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, + instance->serial_num.data, + sizeof(PicopassSerialNum)); + FURI_LOG_D( + TAG, + "csn %02x%02x%02x%02x%02x%02x%02x%02x", + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[0], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[1], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[2], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[3], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[4], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[5], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[6], + instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]); + + PicopassBlock block = {}; + error = picopass_poller_read_block(instance, 1, &block); + if(error != PicopassErrorNone) { + instance->state = PicopassPollerStateFail; + break; + } + memcpy( + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, + block.data, + sizeof(PicopassBlock)); + FURI_LOG_D( + TAG, + "config %02x%02x%02x%02x%02x%02x%02x%02x", + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6], + instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]); + + error = picopass_poller_read_block(instance, 5, &block); + if(error != PicopassErrorNone) { + instance->state = PicopassPollerStateFail; + break; + } + memcpy( + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, + block.data, + sizeof(PicopassBlock)); + FURI_LOG_D( + TAG, + "aia %02x%02x%02x%02x%02x%02x%02x%02x", + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6], + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]); + + instance->state = PicopassPollerStateCheckSecurity; + } while(false); + + return command; +} + +NfcCommand picopass_poller_check_security(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + // Thank you proxmark! + PicopassBlock temp_block = {}; + memset(temp_block.data, 0xff, sizeof(PicopassBlock)); + instance->data->pacs.legacy = + (memcmp( + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, + temp_block.data, + sizeof(PicopassBlock)) == 0); + + temp_block.data[3] = 0x00; + temp_block.data[4] = 0x06; + instance->data->pacs.se_enabled = + (memcmp( + instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, + temp_block.data, + sizeof(PicopassBlock)) == 0); + + if(instance->data->pacs.se_enabled) { + FURI_LOG_D(TAG, "SE enabled"); + instance->state = PicopassPollerStateFail; + } else { + instance->state = PicopassPollerStateAuth; + } + + return command; +} + +NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + // Request key + instance->event.type = PicopassPollerEventTypeRequestKey; + command = instance->callback(instance->event, instance->context); + if(command != NfcCommandContinue) break; + + if(!instance->event_data.req_key.is_key_provided) { + instance->state = PicopassPollerStateFail; + break; + } + + FURI_LOG_D( + TAG, + "Try to %s auth with key %02x%02x%02x%02x%02x%02x%02x%02x", + instance->event_data.req_key.is_elite_key ? "elite" : "standard", + instance->event_data.req_key.key[0], + instance->event_data.req_key.key[1], + instance->event_data.req_key.key[2], + instance->event_data.req_key.key[3], + instance->event_data.req_key.key[4], + instance->event_data.req_key.key[5], + instance->event_data.req_key.key[6], + instance->event_data.req_key.key[7]); + + PicopassReadCheckResp read_check_resp = {}; + uint8_t* csn = instance->serial_num.data; + memset(instance->div_key, 0, sizeof(instance->div_key)); + uint8_t* div_key = NULL; + + if(instance->mode == PicopassPollerModeRead) { + div_key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data; + } else { + div_key = instance->div_key; + } + + uint8_t ccnr[12] = {}; + PicopassMac mac = {}; + + PicopassError error = picopass_poller_read_check(instance, &read_check_resp); + if(error == PicopassErrorTimeout) { + instance->event.type = PicopassPollerEventTypeCardLost; + command = 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 + + loclass_iclass_calc_div_key( + csn, + instance->event_data.req_key.key, + div_key, + instance->event_data.req_key.is_elite_key); + loclass_opt_doReaderMAC(ccnr, div_key, mac.data); + + PicopassCheckResp check_resp = {}; + error = picopass_poller_check(instance, &mac, &check_resp); + if(error == PicopassErrorNone) { + FURI_LOG_I(TAG, "Found key"); + memcpy(instance->mac.data, mac.data, sizeof(PicopassMac)); + if(instance->mode == PicopassPollerModeRead) { + memcpy( + instance->data->pacs.key, instance->event_data.req_key.key, PICOPASS_KEY_LEN); + instance->data->pacs.elite_kdf = instance->event_data.req_key.is_elite_key; + picopass_poller_prepare_read(instance); + instance->state = PicopassPollerStateReadBlock; + } else if(instance->mode == PicopassPollerModeWrite) { + instance->state = PicopassPollerStateWriteBlock; + } else { + instance->state = PicopassPollerStateWriteKey; + } + } + + } while(false); + + return command; +} + +NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + if(instance->current_block == instance->app_limit) { + instance->state = PicopassPollerStateParseCredential; + break; + } + + if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) { + // Skip over Kd block which is populated earlier (READ of Kd returns all FF's) + instance->current_block++; + } + + PicopassBlock block = {}; + PicopassError error = + picopass_poller_read_block(instance, instance->current_block, &block); + if(error != PicopassErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d: %d", instance->current_block, error); + instance->state = PicopassPollerStateFail; + break; + } + FURI_LOG_D( + TAG, + "Block %d: %02x%02x%02x%02x%02x%02x%02x%02x", + instance->current_block, + block.data[0], + block.data[1], + block.data[2], + block.data[3], + block.data[4], + block.data[5], + block.data[6], + block.data[7]); + memcpy( + instance->data->AA1[instance->current_block].data, block.data, sizeof(PicopassBlock)); + instance->current_block++; + } while(false); + + return command; +} + +NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + picopass_device_parse_credential(instance->data->AA1, &instance->data->pacs); + instance->state = PicopassPollerStateParseWiegand; + return command; +} + +NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs); + instance->state = PicopassPollerStateSuccess; + return command; +} + +NfcCommand picopass_poller_write_block_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + PicopassError error = PicopassErrorNone; + + do { + instance->event.type = PicopassPollerEventTypeRequestWriteBlock; + command = instance->callback(instance->event, instance->context); + if(command != NfcCommandContinue) break; + + PicopassPollerEventDataRequestWriteBlock* write_block = &instance->event_data.req_write; + if(!write_block->perform_write) { + instance->state = PicopassPollerStateSuccess; + break; + } + + FURI_LOG_D(TAG, "Writing %d block", write_block->block_num); + uint8_t data[9] = {}; + data[0] = write_block->block_num; + memcpy(&data[1], write_block->block->data, PICOPASS_BLOCK_LEN); + loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data); + FURI_LOG_D( + TAG, + "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", + write_block->block_num, + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + data[8], + instance->mac.data[0], + instance->mac.data[1], + instance->mac.data[2], + instance->mac.data[3]); + error = picopass_poller_write_block( + instance, write_block->block_num, write_block->block, &instance->mac); + if(error != PicopassErrorNone) { + FURI_LOG_E(TAG, "Failed to write block %d. Error %d", write_block->block_num, error); + instance->state = PicopassPollerStateFail; + break; + } + + } while(false); + + return command; +} + +NfcCommand picopass_poller_write_key_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + PicopassError error = PicopassErrorNone; + + do { + instance->event.type = PicopassPollerEventTypeRequestWriteKey; + command = instance->callback(instance->event, instance->context); + if(command != NfcCommandContinue) break; + + const PicopassDeviceData* picopass_data = instance->event_data.req_write_key.data; + const uint8_t* new_key = instance->event_data.req_write_key.key; + bool is_elite_key = instance->event_data.req_write_key.is_elite_key; + + const uint8_t* csn = picopass_data->AA1[PICOPASS_CSN_BLOCK_INDEX].data; + const uint8_t* config_block = picopass_data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; + uint8_t fuses = config_block[7]; + const uint8_t* old_key = picopass_data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data; + + PicopassBlock new_block = {}; + loclass_iclass_calc_div_key(csn, new_key, new_block.data, is_elite_key); + + 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++) { + new_block.data[i] ^= old_key[i]; + } + } + + FURI_LOG_D(TAG, "Writing key to %d block", PICOPASS_SECURE_KD_BLOCK_INDEX); + uint8_t data[9] = {}; + data[0] = PICOPASS_SECURE_KD_BLOCK_INDEX; + memcpy(&data[1], new_block.data, PICOPASS_BLOCK_LEN); + loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data); + FURI_LOG_D( + TAG, + "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", + PICOPASS_SECURE_KD_BLOCK_INDEX, + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + data[8], + instance->mac.data[0], + instance->mac.data[1], + instance->mac.data[2], + instance->mac.data[3]); + error = picopass_poller_write_block( + instance, PICOPASS_SECURE_KD_BLOCK_INDEX, &new_block, &instance->mac); + if(error != PicopassErrorNone) { + FURI_LOG_E( + TAG, "Failed to write block %d. Error %d", PICOPASS_SECURE_KD_BLOCK_INDEX, error); + instance->state = PicopassPollerStateFail; + break; + } + instance->state = PicopassPollerStateSuccess; + + } while(false); + + return command; +} + +NfcCommand picopass_poller_success_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->event.type = PicopassPollerEventTypeSuccess; + command = instance->callback(instance->event, instance->context); + furi_delay_ms(100); + + return command; +} + +NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) { + NfcCommand command = NfcCommandReset; + + instance->event.type = PicopassPollerEventTypeFail; + command = instance->callback(instance->event, instance->context); + picopass_poller_reset(instance); + instance->state = PicopassPollerStateDetect; + + return command; +} + +static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPollerStateNum] = { + [PicopassPollerStateRequestMode] = picopass_poller_request_mode_handler, + [PicopassPollerStateDetect] = picopass_poller_detect_handler, + [PicopassPollerStateSelect] = picopass_poller_select_handler, + [PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler, + [PicopassPollerStateCheckSecurity] = picopass_poller_check_security, + [PicopassPollerStateAuth] = picopass_poller_auth_handler, + [PicopassPollerStateReadBlock] = picopass_poller_read_block_handler, + [PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler, + [PicopassPollerStateWriteKey] = picopass_poller_write_key_handler, + [PicopassPollerStateParseCredential] = picopass_poller_parse_credential_handler, + [PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler, + [PicopassPollerStateSuccess] = picopass_poller_success_handler, + [PicopassPollerStateFail] = picopass_poller_fail_handler, +}; + +static NfcCommand picopass_poller_callback(NfcEvent event, void* context) { + furi_assert(context); + + PicopassPoller* instance = context; + NfcCommand command = NfcCommandContinue; + + if(event.type == NfcEventTypePollerReady) { + command = picopass_poller_state_handler[instance->state](instance); + } + + if(instance->session_state == PicopassPollerSessionStateStopRequest) { + command = NfcCommandStop; + } + + return command; +} + +void picopass_poller_start( + PicopassPoller* instance, + PicopassPollerCallback callback, + void* context) { + furi_assert(instance); + furi_assert(instance->session_state == PicopassPollerSessionStateIdle); + + instance->callback = callback; + instance->context = context; + + instance->session_state = PicopassPollerSessionStateActive; + nfc_start(instance->nfc, picopass_poller_callback, instance); +} + +void picopass_poller_stop(PicopassPoller* instance) { + furi_assert(instance); + + instance->session_state = PicopassPollerSessionStateStopRequest; + nfc_stop(instance->nfc); + instance->session_state = PicopassPollerSessionStateIdle; +} + +PicopassPoller* picopass_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + PicopassPoller* instance = malloc(sizeof(PicopassPoller)); + instance->nfc = nfc; + nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693); + nfc_set_guard_time_us(instance->nfc, 10000); + nfc_set_fdt_poll_fc(instance->nfc, 5000); + nfc_set_fdt_poll_poll_us(instance->nfc, 1000); + + instance->event.data = &instance->event_data; + instance->data = malloc(sizeof(PicopassDeviceData)); + + instance->tx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE); + instance->tmp_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE); + + return instance; +} + +void picopass_poller_free(PicopassPoller* instance) { + furi_assert(instance); + + free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->tmp_buffer); + free(instance); +} + +const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance) { + furi_assert(instance); + + return instance->data; +} diff --git a/picopass/protocol/picopass_poller.h b/picopass/protocol/picopass_poller.h new file mode 100644 index 00000000..39546781 --- /dev/null +++ b/picopass/protocol/picopass_poller.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include "picopass_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PicopassPollerEventTypeRequestMode, + PicopassPollerEventTypeCardDetected, + PicopassPollerEventTypeCardLost, + PicopassPollerEventTypeRequestKey, + PicopassPollerEventTypeRequestWriteBlock, + PicopassPollerEventTypeRequestWriteKey, + PicopassPollerEventTypeSuccess, + PicopassPollerEventTypeFail, +} PicopassPollerEventType; + +typedef enum { + PicopassPollerModeRead, + PicopassPollerModeWrite, + PicopassPollerModeWriteKey, +} PicopassPollerMode; + +typedef struct { + PicopassPollerMode mode; +} PicopassPollerEventDataRequestMode; + +typedef struct { + uint8_t key[PICOPASS_KEY_LEN]; + bool is_key_provided; + bool is_elite_key; +} PicopassPollerEventDataRequestKey; + +typedef struct { + bool perform_write; + uint8_t block_num; + const PicopassBlock* block; +} PicopassPollerEventDataRequestWriteBlock; + +typedef struct { + const PicopassDeviceData* data; + uint8_t key[PICOPASS_KEY_LEN]; + bool is_elite_key; +} PicopassPollerEventDataRequestWriteKey; + +typedef union { + PicopassPollerEventDataRequestMode req_mode; + PicopassPollerEventDataRequestKey req_key; + PicopassPollerEventDataRequestWriteBlock req_write; + PicopassPollerEventDataRequestWriteKey req_write_key; +} PicopassPollerEventData; + +typedef struct { + PicopassPollerEventType type; + PicopassPollerEventData* data; +} PicopassPollerEvent; + +typedef NfcCommand (*PicopassPollerCallback)(PicopassPollerEvent event, void* context); + +typedef struct PicopassPoller PicopassPoller; + +PicopassPoller* picopass_poller_alloc(Nfc* nfc); + +void picopass_poller_free(PicopassPoller* instance); + +void picopass_poller_start( + PicopassPoller* instance, + PicopassPollerCallback callback, + void* context); + +void picopass_poller_stop(PicopassPoller* instance); + +const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/picopass/protocol/picopass_poller_i.c b/picopass/protocol/picopass_poller_i.c new file mode 100644 index 00000000..522cff5d --- /dev/null +++ b/picopass/protocol/picopass_poller_i.c @@ -0,0 +1,217 @@ +#include "picopass_poller_i.h" + +#include + +#define PICOPASS_POLLER_FWT_FC (100000) + +#define TAG "Picopass" + +static PicopassError picopass_poller_process_error(NfcError error) { + PicopassError ret = PicopassErrorNone; + + switch(error) { + case NfcErrorNone: + ret = PicopassErrorNone; + break; + + default: + ret = PicopassErrorTimeout; + break; + } + + return ret; +} + +static PicopassError picopass_poller_send_frame( + PicopassPoller* instance, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + PicopassError ret = PicopassErrorNone; + + do { + NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt_fc); + if(error != NfcErrorNone) { + ret = picopass_poller_process_error(error); + break; + } + if(!iso13239_crc_check(Iso13239CrcTypePicopass, rx_buffer)) { + ret = PicopassErrorIncorrectCrc; + break; + } + iso13239_crc_trim(instance->rx_buffer); + } while(false); + + return ret; +} + +PicopassError picopass_poller_actall(PicopassPoller* instance) { + PicopassError ret = PicopassErrorNone; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_ACTALL); + + NfcError error = nfc_poller_trx( + instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(error != NfcErrorIncompleteFrame) { + ret = picopass_poller_process_error(error); + } + + return ret; +} + +PicopassError picopass_poller_identify( + PicopassPoller* instance, + PicopassColResSerialNum* col_res_serial_num) { + PicopassError ret = PicopassErrorNone; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY); + ret = picopass_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(ret != PicopassErrorNone) break; + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassColResSerialNum)) { + ret = PicopassErrorProtocol; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum)); + } while(false); + + return ret; +} + +PicopassError picopass_poller_select( + PicopassPoller* instance, + PicopassColResSerialNum* col_res_serial_num, + PicopassSerialNum* serial_num) { + PicopassError ret = PicopassErrorNone; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_SELECT); + bit_buffer_append_bytes( + instance->tx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum)); + ret = picopass_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(ret != PicopassErrorNone) break; + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassSerialNum)) { + ret = PicopassErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, serial_num->data, sizeof(PicopassSerialNum)); + } while(false); + + return ret; +} + +PicopassError + picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block) { + PicopassError ret = PicopassErrorNone; + + do { + bit_buffer_reset(instance->tmp_buffer); + bit_buffer_append_byte(instance->tmp_buffer, block_num); + iso13239_crc_append(Iso13239CrcTypePicopass, instance->tmp_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY); + bit_buffer_append(instance->tx_buffer, instance->tmp_buffer); + + ret = picopass_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(ret != PicopassErrorNone) break; + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassBlock)) { + ret = PicopassErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, block->data, sizeof(PicopassBlock)); + } while(false); + + return ret; +} + +PicopassError + picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp) { + PicopassError ret = PicopassErrorNone; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READCHECK_KD); + bit_buffer_append_byte(instance->tx_buffer, 0x02); + + NfcError error = nfc_poller_trx( + instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(error != NfcErrorNone) { + ret = picopass_poller_process_error(error); + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassReadCheckResp)) { + ret = PicopassErrorProtocol; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, read_check_resp->data, sizeof(PicopassReadCheckResp)); + } while(false); + + return ret; +} + +PicopassError picopass_poller_check( + PicopassPoller* instance, + PicopassMac* mac, + PicopassCheckResp* check_resp) { + PicopassError ret = PicopassErrorNone; + + 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)); + bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac)); + + NfcError error = nfc_poller_trx( + instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(error != NfcErrorNone) { + ret = picopass_poller_process_error(error); + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassCheckResp)) { + ret = PicopassErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, check_resp->data, sizeof(PicopassCheckResp)); + + } while(false); + + return ret; +} + +PicopassError picopass_poller_write_block( + PicopassPoller* instance, + uint8_t block_num, + const PicopassBlock* block, + const PicopassMac* mac) { + PicopassError ret = PicopassErrorNone; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_UPDATE); + bit_buffer_append_byte(instance->tx_buffer, block_num); + bit_buffer_append_bytes(instance->tx_buffer, block->data, sizeof(PicopassBlock)); + bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac)); + + NfcError error = nfc_poller_trx( + instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC); + if(error != NfcErrorNone) { + ret = picopass_poller_process_error(error); + break; + } + + } while(false); + + return ret; +} diff --git a/picopass/protocol/picopass_poller_i.h b/picopass/protocol/picopass_poller_i.h new file mode 100644 index 00000000..0d0b7373 --- /dev/null +++ b/picopass/protocol/picopass_poller_i.h @@ -0,0 +1,85 @@ +#pragma once + +#include "picopass_poller.h" +#include "picopass_protocol.h" + +#include + +#define PICOPASS_POLLER_BUFFER_SIZE (255) +#define PICOPASS_CRC_SIZE (2) + +typedef enum { + PicopassPollerSessionStateIdle, + PicopassPollerSessionStateActive, + PicopassPollerSessionStateStopRequest, +} PicopassPollerSessionState; + +typedef enum { + PicopassPollerStateRequestMode, + PicopassPollerStateDetect, + PicopassPollerStateSelect, + PicopassPollerStatePreAuth, + PicopassPollerStateCheckSecurity, + PicopassPollerStateAuth, + PicopassPollerStateReadBlock, + PicopassPollerStateWriteBlock, + PicopassPollerStateWriteKey, + PicopassPollerStateParseCredential, + PicopassPollerStateParseWiegand, + PicopassPollerStateSuccess, + PicopassPollerStateFail, + + PicopassPollerStateNum, +} PicopassPollerState; + +struct PicopassPoller { + Nfc* nfc; + PicopassPollerSessionState session_state; + PicopassPollerState state; + PicopassPollerMode mode; + + PicopassColResSerialNum col_res_serial_num; + PicopassSerialNum serial_num; + PicopassMac mac; + uint8_t div_key[8]; + uint8_t current_block; + uint8_t app_limit; + + PicopassDeviceData* data; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* tmp_buffer; + + PicopassPollerEvent event; + PicopassPollerEventData event_data; + PicopassPollerCallback callback; + void* context; +}; + +PicopassError picopass_poller_actall(PicopassPoller* instance); + +PicopassError + picopass_poller_identify(PicopassPoller* instance, PicopassColResSerialNum* col_res_serial_num); + +PicopassError picopass_poller_select( + PicopassPoller* instance, + PicopassColResSerialNum* col_res_serial_num, + PicopassSerialNum* serial_num); + +PicopassError + picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block); + +PicopassError + picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp); + +PicopassError picopass_poller_check( + PicopassPoller* instance, + PicopassMac* mac, + PicopassCheckResp* check_resp); + +PicopassError picopass_poller_write_block( + PicopassPoller* instance, + uint8_t block_num, + const PicopassBlock* block, + const PicopassMac* mac); diff --git a/picopass/protocol/picopass_protocol.h b/picopass/protocol/picopass_protocol.h new file mode 100644 index 00000000..62762cdb --- /dev/null +++ b/picopass/protocol/picopass_protocol.h @@ -0,0 +1,48 @@ +#pragma once + +#include "../picopass_device.h" + +#define PICOPASS_BLOCK_LEN 8 +#define PICOPASS_MAX_APP_LIMIT 32 +#define PICOPASS_UID_LEN 8 +#define PICOPASS_READ_CHECK_RESP_LEN 8 +#define PICOPASS_CHECK_RESP_LEN 4 +#define PICOPASS_MAC_LEN 4 +#define PICOPASS_KEY_LEN 8 + +#define PICOPASS_FDT_LISTEN_FC (1000) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PicopassErrorNone, + PicopassErrorTimeout, + PicopassErrorIncorrectCrc, + PicopassErrorProtocol, +} PicopassError; + +typedef struct { + uint8_t data[RFAL_PICOPASS_UID_LEN]; +} PicopassColResSerialNum; + +typedef struct { + uint8_t data[RFAL_PICOPASS_UID_LEN]; +} PicopassSerialNum; + +typedef struct { + uint8_t data[PICOPASS_READ_CHECK_RESP_LEN]; +} PicopassReadCheckResp; + +typedef struct { + uint8_t data[PICOPASS_CHECK_RESP_LEN]; +} PicopassCheckResp; + +typedef struct { + uint8_t data[PICOPASS_MAC_LEN]; +} PicopassMac; + +#ifdef __cplusplus +} +#endif diff --git a/picopass/rfal_picopass.c b/picopass/rfal_picopass.c deleted file mode 100644 index 08be5e3f..00000000 --- a/picopass/rfal_picopass.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "rfal_picopass.h" - -#define RFAL_PICOPASS_TXRX_FLAGS \ - (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ - FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP) - -#define TAG "RFAL_PICOPASS" - -typedef struct { - uint8_t CMD; - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; -} rfalPicoPassSelectReq; - -typedef struct { - uint8_t CMD; - uint8_t null[4]; - uint8_t mac[4]; -} rfalPicoPassCheckReq; - -static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) { - uint16_t crc = crcSeed; - uint8_t dat = dataByte; - - dat ^= (uint8_t)(crc & 0xFFU); - dat ^= (dat << 4); - - crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4); - - return crc; -} - -uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) { - uint16_t crc = preloadValue; - uint16_t index; - - for(index = 0; index < length; index++) { - crc = rfalPicoPassUpdateCcitt(crc, buf[index]); - } - - return crc; -} - -FuriHalNfcReturn rfalPicoPassPollerInitialize(void) { - FuriHalNfcReturn ret; - - ret = furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48); - if(ret != FuriHalNfcReturnOk) { - return ret; - }; - - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS); - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER); - - return FuriHalNfcReturnOk; -} - -FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) { - FuriHalNfcReturn ret; - uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL}; - uint8_t rxBuf[32] = {0}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { - FuriHalNfcReturn ret; - - uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)idRes, - sizeof(rfalPicoPassIdentifyRes), - &recvLen, - flags, - fwt); - // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN)); - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { - FuriHalNfcReturn ret; - - rfalPicoPassSelectReq selReq; - selReq.CMD = RFAL_PICOPASS_CMD_SELECT; - memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - (uint8_t*)&selReq, - sizeof(rfalPicoPassSelectReq), - (uint8_t*)selRes, - sizeof(rfalPicoPassSelectRes), - &recvLen, - flags, - fwt); - // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN)); - if(ret == FuriHalNfcReturnTimeout) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { - FuriHalNfcReturn ret; - uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK_KD, 0x02}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)rcRes, - sizeof(rfalPicoPassReadCheckRes), - &recvLen, - flags, - fwt); - // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8)); - - if(ret == FuriHalNfcReturnCrc) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { - FuriHalNfcReturn ret; - rfalPicoPassCheckReq chkReq; - chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; - memcpy(chkReq.mac, mac, 4); - memset(chkReq.null, 0, 4); - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq))); - ret = furi_hal_nfc_ll_txrx( - (uint8_t*)&chkReq, - sizeof(rfalPicoPassCheckReq), - (uint8_t*)chkRes, - sizeof(rfalPicoPassCheckRes), - &recvLen, - flags, - fwt); - // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4)); - if(ret == FuriHalNfcReturnCrc) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { - FuriHalNfcReturn ret; - - uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, 0, 0, 0}; - txBuf[1] = blockNum; - uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1); - memcpy(txBuf + 2, &crc, sizeof(uint16_t)); - - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)readRes, - sizeof(rfalPicoPassReadBlockRes), - &recvLen, - flags, - fwt); - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { - FuriHalNfcReturn ret; - - uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_UPDATE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - memcpy(txBuf + 2, data, PICOPASS_BLOCK_LEN); - memcpy(txBuf + 10, mac, 4); - - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - rfalPicoPassReadBlockRes block; - - ret = furi_hal_nfc_ll_txrx( - txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt); - - if(ret == FuriHalNfcReturnOk) { - // TODO: compare response - } - - return ret; -} diff --git a/picopass/rfal_picopass.h b/picopass/rfal_picopass.h index 82da9720..9dd0cbf5 100644 --- a/picopass/rfal_picopass.h +++ b/picopass/rfal_picopass.h @@ -62,14 +62,3 @@ typedef struct { uint8_t data[PICOPASS_BLOCK_LEN]; uint8_t crc[2]; } rfalPicoPassReadBlockRes; - -uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length); - -FuriHalNfcReturn rfalPicoPassPollerInitialize(void); -FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void); -FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); -FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); -FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); -FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); -FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); -FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); diff --git a/picopass/scenes/picopass_scene_elite_dict_attack.c b/picopass/scenes/picopass_scene_elite_dict_attack.c index e6191d5b..847658bd 100644 --- a/picopass/scenes/picopass_scene_elite_dict_attack.c +++ b/picopass/scenes/picopass_scene_elite_dict_attack.c @@ -1,172 +1,238 @@ #include "../picopass_i.h" #include +#include "../picopass_keys.h" -#define TAG "IclassEliteDictAttack" +#define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10) -typedef enum { - DictAttackStateIdle, - DictAttackStateUserDictInProgress, - DictAttackStateFlipperDictInProgress, - DictAttackStateStandardDictInProgress, -} DictAttackState; +enum { + PicopassSceneEliteDictAttackDictEliteUser, + PicopassSceneEliteDictAttackDictStandart, + PicopassSceneEliteDictAttackDictElite, +}; -void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) { - furi_assert(context); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, event); +const char* picopass_dict_name[] = { + [PicopassSceneEliteDictAttackDictEliteUser] = "Elite User Dictionary", + [PicopassSceneEliteDictAttackDictStandart] = "Standard System Dictionary", + [PicopassSceneEliteDictAttackDictElite] = "Elite System Dictionary", +}; + +static bool picopass_elite_dict_attack_change_dict(Picopass* picopass) { + bool success = false; + + do { + uint32_t scene_state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); + nfc_dict_free(picopass->dict); + picopass->dict = NULL; + if(scene_state == PicopassSceneEliteDictAttackDictElite) break; + if(scene_state == PicopassSceneEliteDictAttackDictEliteUser) { + if(!nfc_dict_check_presence(PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME)) break; + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, + NfcDictModeOpenExisting, + PICOPASS_KEY_LEN); + scene_state = PicopassSceneEliteDictAttackDictStandart; + } else if(scene_state == PicopassSceneEliteDictAttackDictStandart) { + if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break; + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME, + NfcDictModeOpenExisting, + PICOPASS_KEY_LEN); + scene_state = PicopassSceneEliteDictAttackDictElite; + } + picopass->dict_attack_ctx.card_detected = true; + picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict); + picopass->dict_attack_ctx.current_key = 0; + picopass->dict_attack_ctx.name = picopass_dict_name[scene_state]; + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneEliteDictAttack, scene_state); + success = true; + } while(false); + + return success; } -void picopass_dict_attack_result_callback(void* context) { +NfcCommand picopass_elite_dict_attack_worker_callback(PicopassPollerEvent event, void* context) { furi_assert(context); + NfcCommand command = NfcCommandContinue; + Picopass* picopass = context; - view_dispatcher_send_custom_event( - picopass->view_dispatcher, PicopassCustomEventDictAttackSkip); -} -static void - picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) { - IclassEliteDictAttackData* dict_attack_data = - &picopass->dev->dev_data.iclass_elite_dict_attack_data; - PicopassWorkerState worker_state = PicopassWorkerStateReady; - IclassEliteDict* dict = NULL; - - // Identify scene state - if(state == DictAttackStateIdle) { - if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) { - FURI_LOG_D(TAG, "Starting with user dictionary"); - state = DictAttackStateUserDictInProgress; - } else { - FURI_LOG_D(TAG, "Starting with standard dictionary"); - state = DictAttackStateStandardDictInProgress; + if(event.type == PicopassPollerEventTypeRequestMode) { + event.data->req_mode.mode = PicopassPollerModeRead; + } else if(event.type == PicopassPollerEventTypeRequestKey) { + uint8_t key[PICOPASS_KEY_LEN] = {}; + bool is_key_provided = true; + if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) { + if(picopass_elite_dict_attack_change_dict(picopass)) { + is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN); + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView); + } else { + is_key_provided = false; + } } - } else if(state == DictAttackStateUserDictInProgress) { - FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary"); - state = DictAttackStateStandardDictInProgress; - } else if(state == DictAttackStateStandardDictInProgress) { - FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary"); - state = DictAttackStateFlipperDictInProgress; - } - - // Setup view - if(state == DictAttackStateUserDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary"); - dict_attack_data->type = IclassEliteDictTypeUser; - dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser); - - // If failed to load user dictionary - try the system dictionary - if(!dict) { - FURI_LOG_E(TAG, "User dictionary not found"); - state = DictAttackStateStandardDictInProgress; + uint32_t scene_state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); + memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN); + event.data->req_key.is_elite_key = + (scene_state != PicopassSceneEliteDictAttackDictStandart); + event.data->req_key.is_key_provided = is_key_provided; + if(is_key_provided) { + picopass->dict_attack_ctx.current_key++; + if(picopass->dict_attack_ctx.current_key % + PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE == + 0) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView); + } } + } else if(event.type == PicopassPollerEventTypeSuccess) { + const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller); + memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData)); + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } else if(event.type == PicopassPollerEventTypeFail) { + const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller); + memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData)); + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } else if(event.type == PicopassPollerEventTypeCardLost) { + picopass->dict_attack_ctx.card_detected = false; + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView); + } else if(event.type == PicopassPollerEventTypeCardDetected) { + picopass->dict_attack_ctx.card_detected = true; + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView); } - if(state == DictAttackStateStandardDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary"); - dict_attack_data->type = IclassStandardDictTypeFlipper; - dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper); - - if(!dict) { - FURI_LOG_E(TAG, "Flipper standard dictionary not found"); - state = DictAttackStateFlipperDictInProgress; - } + + return command; +} + +static void picopass_scene_elite_dict_attack_update_view(Picopass* instance) { + if(instance->dict_attack_ctx.card_detected) { + dict_attack_set_card_detected(instance->dict_attack); + dict_attack_set_header(instance->dict_attack, instance->dict_attack_ctx.name); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->dict_attack_ctx.total_keys); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->dict_attack_ctx.current_key); + } else { + dict_attack_set_card_removed(instance->dict_attack); } - if(state == DictAttackStateFlipperDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary"); - dict_attack_data->type = IclassEliteDictTypeFlipper; - dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); - if(!dict) { - FURI_LOG_E(TAG, "Flipper Elite dictionary not found"); - // Pass through to let the worker handle the failure +} + +static void picopass_scene_elite_dict_attack_callback(void* context) { + Picopass* instance = context; + + view_dispatcher_send_custom_event( + instance->view_dispatcher, PicopassCustomEventDictAttackSkip); +} + +void picopass_scene_elite_dict_attack_on_enter(void* context) { + Picopass* picopass = context; + dolphin_deed(DolphinDeedNfcRead); + + // Setup dict attack context + uint32_t state = PicopassSceneEliteDictAttackDictEliteUser; + + bool use_user_dict = nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_USER_NAME); + if(use_user_dict) { + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_ELITE_DICT_USER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN); + if(nfc_dict_get_total_keys(picopass->dict) == 0) { + nfc_dict_free(picopass->dict); + use_user_dict = false; } } - // Free previous dictionary - if(dict_attack_data->dict) { - iclass_elite_dict_free(dict_attack_data->dict); + if(use_user_dict) { + state = PicopassSceneEliteDictAttackDictEliteUser; + } else { + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN); + state = PicopassSceneEliteDictAttackDictStandart; } - dict_attack_data->dict = dict; + picopass->dict_attack_ctx.card_detected = true; + picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict); + picopass->dict_attack_ctx.current_key = 0; + picopass->dict_attack_ctx.name = picopass_dict_name[state]; scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state); + + // Setup view + picopass_scene_elite_dict_attack_update_view(picopass); dict_attack_set_callback( - picopass->dict_attack, picopass_dict_attack_result_callback, picopass); - dict_attack_set_current_sector(picopass->dict_attack, 0); - dict_attack_set_card_detected(picopass->dict_attack); - dict_attack_set_total_dict_keys( - picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0); - picopass_worker_start( - picopass->worker, - worker_state, - &picopass->dev->dev_data, - picopass_dict_attack_worker_callback, - picopass); -} + picopass->dict_attack, picopass_scene_elite_dict_attack_callback, picopass); + + // Start worker + picopass->poller = picopass_poller_alloc(picopass->nfc); + picopass_poller_start(picopass->poller, picopass_elite_dict_attack_worker_callback, picopass); -void picopass_scene_elite_dict_attack_on_enter(void* context) { - Picopass* picopass = context; - picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack); picopass_blink_start(picopass); - notification_message(picopass->notifications, &sequence_display_backlight_enforce_on); } bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) { Picopass* picopass = context; bool consumed = false; - uint32_t state = - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassWorkerEventSuccess) { - if(state == DictAttackStateUserDictInProgress || - state == DictAttackStateStandardDictInProgress) { - picopass_worker_stop(picopass->worker); - picopass_scene_elite_dict_attack_prepare_view(picopass, state); - consumed = true; + if(event.event == PicopassCustomEventPollerSuccess) { + if(memcmp( + picopass->dev->dev_data.pacs.key, + picopass_factory_debit_key, + PICOPASS_BLOCK_LEN) == 0) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); } else { scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); - consumed = true; } - } else if(event.event == PicopassWorkerEventAborted) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); - consumed = true; - } else if(event.event == PicopassWorkerEventCardDetected) { - dict_attack_set_card_detected(picopass->dict_attack); consumed = true; - } else if(event.event == PicopassWorkerEventNoCardDetected) { - dict_attack_set_card_removed(picopass->dict_attack); - consumed = true; - } else if(event.event == PicopassWorkerEventNewDictKeyBatch) { - dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE); + } else if(event.event == PicopassCustomEventDictAttackUpdateView) { + picopass_scene_elite_dict_attack_update_view(picopass); consumed = true; } else if(event.event == PicopassCustomEventDictAttackSkip) { - if(state == DictAttackStateUserDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; - } else if(state == DictAttackStateFlipperDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; - } else if(state == DictAttackStateStandardDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; + uint32_t scene_state = scene_manager_get_scene_state( + picopass->scene_manager, PicopassSceneEliteDictAttack); + if(scene_state != PicopassSceneEliteDictAttackDictElite) { + picopass_elite_dict_attack_change_dict(picopass); + picopass_scene_elite_dict_attack_update_view(picopass); + } else { + if(memcmp( + picopass->dev->dev_data.pacs.key, + picopass_factory_debit_key, + PICOPASS_BLOCK_LEN) == 0) { + scene_manager_next_scene( + picopass->scene_manager, PicopassSceneReadFactorySuccess); + } else { + scene_manager_next_scene( + picopass->scene_manager, PicopassSceneReadCardSuccess); + } } + consumed = true; } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(picopass->scene_manager); } return consumed; } void picopass_scene_elite_dict_attack_on_exit(void* context) { Picopass* picopass = context; - IclassEliteDictAttackData* dict_attack_data = - &picopass->dev->dev_data.iclass_elite_dict_attack_data; - // Stop worker - picopass_worker_stop(picopass->worker); - if(dict_attack_data->dict) { - iclass_elite_dict_free(dict_attack_data->dict); - dict_attack_data->dict = NULL; + + if(picopass->dict) { + nfc_dict_free(picopass->dict); + picopass->dict = NULL; } - dict_attack_reset(picopass->dict_attack); + picopass->dict_attack_ctx.current_key = 0; + picopass->dict_attack_ctx.total_keys = 0; + + picopass_poller_stop(picopass->poller); + picopass_poller_free(picopass->poller); + + // Clear view + popup_reset(picopass->popup); + scene_manager_set_scene_state( + picopass->scene_manager, + PicopassSceneEliteDictAttack, + PicopassSceneEliteDictAttackDictEliteUser); + picopass_blink_stop(picopass); - notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto); } diff --git a/picopass/scenes/picopass_scene_emulate.c b/picopass/scenes/picopass_scene_emulate.c index 4e0ed073..8f802c65 100644 --- a/picopass/scenes/picopass_scene_emulate.c +++ b/picopass/scenes/picopass_scene_emulate.c @@ -1,10 +1,11 @@ #include "../picopass_i.h" #include -void picopass_emulate_worker_callback(PicopassWorkerEvent event, void* context) { - furi_assert(context); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, event); +NfcCommand picopass_scene_listener_callback(PicopassListenerEvent event, void* context) { + UNUSED(event); + UNUSED(context); + + return NfcCommandContinue; } void picopass_scene_emulate_on_enter(void* context) { @@ -17,18 +18,11 @@ void picopass_scene_emulate_on_enter(void* context) { widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating"); widget_add_string_element(widget, 89, 42, AlignCenter, AlignTop, FontPrimary, "PicoPass"); - // Setup view view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); - - // Start worker - picopass_worker_start( - picopass->worker, - PicopassWorkerStateEmulate, - &picopass->dev->dev_data, - picopass_emulate_worker_callback, - picopass); - picopass_blink_emulate_start(picopass); + + picopass->listener = picopass_listener_alloc(picopass->nfc, &picopass->dev->dev_data); + picopass_listener_start(picopass->listener, picopass_scene_listener_callback, picopass); } bool picopass_scene_emulate_on_event(void* context, SceneManagerEvent event) { @@ -49,9 +43,8 @@ void picopass_scene_emulate_on_exit(void* context) { Picopass* picopass = context; picopass_blink_stop(picopass); - - // Stop worker - picopass_worker_stop(picopass->worker); + picopass_listener_stop(picopass->listener); + picopass_listener_free(picopass->listener); // Clear view widget_reset(picopass->widget); diff --git a/picopass/scenes/picopass_scene_key_input.c b/picopass/scenes/picopass_scene_key_input.c index afddb794..a4979340 100644 --- a/picopass/scenes/picopass_scene_key_input.c +++ b/picopass/scenes/picopass_scene_key_input.c @@ -6,8 +6,8 @@ void picopass_scene_key_input_text_input_callback(void* context) { Picopass* picopass = context; - picopass->dev->dev_data.pacs.elite_kdf = true; - memcpy(picopass->dev->dev_data.pacs.key, picopass->byte_input_store, PICOPASS_BLOCK_LEN); + memcpy(picopass->write_key_context.key_to_write, picopass->byte_input_store, PICOPASS_KEY_LEN); + picopass->write_key_context.is_elite = true; view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventByteInputDone); } diff --git a/picopass/scenes/picopass_scene_key_menu.c b/picopass/scenes/picopass_scene_key_menu.c index 07975df8..31327f69 100644 --- a/picopass/scenes/picopass_scene_key_menu.c +++ b/picopass/scenes/picopass_scene_key_menu.c @@ -65,29 +65,30 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexWriteStandard) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard); - memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; + memcpy( + picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_KEY_LEN); + picopass->write_key_context.is_elite = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCE) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = true; + memcpy(picopass->write_key_context.key_to_write, picopass_xice_key, PICOPASS_KEY_LEN); + picopass->write_key_context.is_elite = true; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCL) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; + memcpy(picopass->write_key_context.key_to_write, picopass_xicl_key, PICOPASS_KEY_LEN); + picopass->write_key_context.is_elite = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCS) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; + memcpy(picopass->write_key_context.key_to_write, picopass_xics_key, PICOPASS_KEY_LEN); + picopass->write_key_context.is_elite = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteCustom) { diff --git a/picopass/scenes/picopass_scene_loclass.c b/picopass/scenes/picopass_scene_loclass.c index d4e26543..94430591 100644 --- a/picopass/scenes/picopass_scene_loclass.c +++ b/picopass/scenes/picopass_scene_loclass.c @@ -1,88 +1,103 @@ #include "../picopass_i.h" #include -void picopass_loclass_worker_callback(PicopassWorkerEvent event, void* context) { - furi_assert(context); +static NfcCommand + picopass_scene_loclass_listener_callback(PicopassListenerEvent event, void* context) { + NfcCommand command = NfcCommandContinue; + Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, event); + + if(event.type == PicopassListenerEventTypeLoclassGotMac) { + picopass->loclass_context.macs_collected++; + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventLoclassGotMac); + } else if(event.type == PicopassListenerEventTypeLoclassGotStandardKey) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventLoclassGotStandardKey); + command = NfcCommandStop; + } + + return command; } -void picopass_loclass_result_callback(void* context) { +static void picopass_loclass_result_callback(void* context) { furi_assert(context); + Picopass* picopass = context; view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); } void picopass_scene_loclass_on_enter(void* context) { Picopass* picopass = context; + dolphin_deed(DolphinDeedNfcEmulate); scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 0); loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass); - - // Start worker - picopass_worker_start( - picopass->worker, - PicopassWorkerStateLoclass, - &picopass->dev->dev_data, - picopass_loclass_worker_callback, - picopass); - - picopass_blink_emulate_start(picopass); - loclass_set_header(picopass->loclass, "Loclass"); + picopass_blink_emulate_start(picopass); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass); + + PicopassDeviceData* data = malloc(sizeof(PicopassDeviceData)); + const uint8_t config_block[PICOPASS_BLOCK_LEN] = { + 0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C}; + memcpy(data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, config_block, sizeof(config_block)); + + const uint8_t epurse[PICOPASS_BLOCK_LEN] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + memcpy(data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, epurse, sizeof(epurse)); + + const uint8_t aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + memcpy(data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia, sizeof(aia)); + + picopass->listener = picopass_listener_alloc(picopass->nfc, data); + free(data); + if(picopass_listener_set_mode(picopass->listener, PicopassListenerModeLoclass)) { + picopass_listener_start( + picopass->listener, picopass_scene_loclass_listener_callback, picopass); + } else { + loclass_set_num_macs(picopass->loclass, 255); + loclass_set_header(picopass->loclass, "Error Opening Log File"); + picopass_listener_free(picopass->listener); + picopass->listener = NULL; + } } bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; bool consumed = false; + Picopass* picopass = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassWorkerEventLoclassGotMac) { - uint32_t loclass_macs_collected = - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass); - loclass_macs_collected++; + if(event.event == PicopassCustomEventLoclassGotMac) { notification_message(picopass->notifications, &sequence_single_vibro); - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected); - loclass_set_num_macs(picopass->loclass, loclass_macs_collected); - if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) { + loclass_set_num_macs(picopass->loclass, picopass->loclass_context.macs_collected); + if(picopass->loclass_context.macs_collected >= LOCLASS_MACS_TO_COLLECT) { notification_message(picopass->notifications, &sequence_double_vibro); scene_manager_previous_scene(picopass->scene_manager); } consumed = true; - } else if(event.event == PicopassWorkerEventLoclassGotStandardKey) { + } else if(event.event == PicopassCustomEventLoclassGotStandardKey) { loclass_set_header(picopass->loclass, "Loclass (Got Std Key)"); notification_message(picopass->notifications, &sequence_error); consumed = true; - } else if(event.event == PicopassWorkerEventLoclassFileError) { - scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255); - loclass_set_num_macs(picopass->loclass, 255); - loclass_set_header(picopass->loclass, "Error Opening Log File"); - picopass_blink_stop(picopass); - consumed = true; } else if(event.event == PicopassCustomEventViewExit) { consumed = scene_manager_previous_scene(picopass->scene_manager); } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(picopass->scene_manager); } + return consumed; } void picopass_scene_loclass_on_exit(void* context) { Picopass* picopass = context; + if(picopass->listener) { + picopass_listener_stop(picopass->listener); + picopass_listener_free(picopass->listener); + } + picopass->loclass_context.macs_collected = 0; picopass_blink_stop(picopass); - // Stop worker - picopass_worker_stop(picopass->worker); - loclass_reset(picopass->loclass); - - // Clear view - widget_reset(picopass->widget); -} +} \ No newline at end of file diff --git a/picopass/scenes/picopass_scene_read_card.c b/picopass/scenes/picopass_scene_read_card.c index c1cc7249..bfa96c8a 100644 --- a/picopass/scenes/picopass_scene_read_card.c +++ b/picopass/scenes/picopass_scene_read_card.c @@ -2,10 +2,68 @@ #include #include "../picopass_keys.h" -void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); +enum { + PicopassSceneReadCardDictStandart, + PicopassSceneReadCardDictElite, +}; + +static bool picopass_read_card_change_dict(Picopass* picopass) { + bool success = false; + + do { + uint32_t scene_state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard); + nfc_dict_free(picopass->dict); + picopass->dict = NULL; + if(scene_state == PicopassSceneReadCardDictElite) break; + if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break; + + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN); + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictElite); + success = true; + } while(false); + + return success; +} + +NfcCommand picopass_read_card_worker_callback(PicopassPollerEvent event, void* context) { + furi_assert(context); + NfcCommand command = NfcCommandContinue; + Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); + + if(event.type == PicopassPollerEventTypeRequestMode) { + event.data->req_mode.mode = PicopassPollerModeRead; + } else if(event.type == PicopassPollerEventTypeRequestKey) { + uint8_t key[PICOPASS_KEY_LEN] = {}; + bool is_key_provided = true; + if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) { + if(picopass_read_card_change_dict(picopass)) { + is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN); + } else { + is_key_provided = false; + } + } + uint32_t scene_state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard); + memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN); + event.data->req_key.is_elite_key = (scene_state == PicopassSceneReadCardDictElite); + event.data->req_key.is_key_provided = is_key_provided; + } else if(event.type == PicopassPollerEventTypeSuccess) { + const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller); + memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData)); + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } else if(event.type == PicopassPollerEventTypeFail) { + const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller); + memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData)); + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } + + return command; } void picopass_scene_read_card_on_enter(void* context) { @@ -17,15 +75,15 @@ void picopass_scene_read_card_on_enter(void* context) { popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop); popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + picopass->dict = nfc_dict_alloc( + PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN); + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart); // Start worker - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateDetect, - &picopass->dev->dev_data, - picopass_read_card_worker_callback, - picopass); + picopass->poller = picopass_poller_alloc(picopass->nfc); + picopass_poller_start(picopass->poller, picopass_read_card_worker_callback, picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); picopass_blink_start(picopass); } @@ -34,7 +92,7 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventWorkerExit) { + if(event.event == PicopassCustomEventPollerSuccess) { if(memcmp( picopass->dev->dev_data.pacs.key, picopass_factory_debit_key, @@ -52,10 +110,17 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { void picopass_scene_read_card_on_exit(void* context) { Picopass* picopass = context; - // Stop worker - picopass_worker_stop(picopass->worker); + if(picopass->dict) { + nfc_dict_free(picopass->dict); + picopass->dict = NULL; + } + picopass_poller_stop(picopass->poller); + picopass_poller_free(picopass->poller); + // Clear view popup_reset(picopass->popup); + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart); picopass_blink_stop(picopass); } diff --git a/picopass/scenes/picopass_scene_read_card_success.c b/picopass/scenes/picopass_scene_read_card_success.c index 67442d64..8579d338 100644 --- a/picopass/scenes/picopass_scene_read_card_success.c +++ b/picopass/scenes/picopass_scene_read_card_success.c @@ -1,5 +1,6 @@ #include "../picopass_i.h" #include +#include void picopass_scene_read_card_success_widget_callback( GuiButtonType result, diff --git a/picopass/scenes/picopass_scene_read_factory_success.c b/picopass/scenes/picopass_scene_read_factory_success.c index f5fcd10f..fa78f2e6 100644 --- a/picopass/scenes/picopass_scene_read_factory_success.c +++ b/picopass/scenes/picopass_scene_read_factory_success.c @@ -64,7 +64,9 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(picopass->scene_manager); } else if(event.event == GuiButtonTypeCenter) { - memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + memcpy( + picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_BLOCK_LEN); + picopass->write_key_context.is_elite = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } diff --git a/picopass/scenes/picopass_scene_write_card.c b/picopass/scenes/picopass_scene_write_card.c index 3c6eae29..edc490bd 100644 --- a/picopass/scenes/picopass_scene_write_card.c +++ b/picopass/scenes/picopass_scene_write_card.c @@ -1,10 +1,42 @@ #include "../picopass_i.h" #include +#include "../picopass_keys.h" -void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); +#define PICOPASS_SCENE_WRITE_BLOCK_START 6 +#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10 + +NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void* context) { Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, event); + NfcCommand command = NfcCommandContinue; + + if(event.type == PicopassPollerEventTypeRequestMode) { + event.data->req_mode.mode = PicopassPollerModeWrite; + } else if(event.type == PicopassPollerEventTypeRequestKey) { + memcpy(event.data->req_key.key, picopass_iclass_key, sizeof(picopass_iclass_key)); + event.data->req_key.is_elite_key = false; + event.data->req_key.is_key_provided = true; + } else if(event.type == PicopassPollerEventTypeRequestWriteBlock) { + uint8_t block_num = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneWriteCard); + if(block_num == PICOPASS_SCENE_WRITE_BLOCK_STOP) { + event.data->req_write.perform_write = false; + } else { + event.data->req_write.block_num = block_num; + event.data->req_write.block = &picopass->dev->dev_data.AA1[block_num]; + event.data->req_write.perform_write = true; + block_num++; + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneWriteCard, block_num); + } + } else if(event.type == PicopassPollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } else if(event.type == PicopassPollerEventTypeFail) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerFail); + } + + return command; } void picopass_scene_write_card_on_enter(void* context) { @@ -15,15 +47,14 @@ void picopass_scene_write_card_on_enter(void* context) { Popup* popup = picopass->popup; popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop); popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneWriteCard, PICOPASS_SCENE_WRITE_BLOCK_START); // Start worker view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateWrite, - &picopass->dev->dev_data, - picopass_write_card_worker_callback, - picopass); + + picopass->poller = picopass_poller_alloc(picopass->nfc); + picopass_poller_start(picopass->poller, picopass_scene_write_poller_callback, picopass); picopass_blink_start(picopass); } @@ -33,10 +64,10 @@ bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event) bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassWorkerEventFail) { + if(event.event == PicopassCustomEventPollerFail) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure); consumed = true; - } else if(event.event == PicopassWorkerEventSuccess) { + } else if(event.event == PicopassCustomEventPollerSuccess) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); consumed = true; } @@ -48,7 +79,8 @@ void picopass_scene_write_card_on_exit(void* context) { Picopass* picopass = context; // Stop worker - picopass_worker_stop(picopass->worker); + picopass_poller_stop(picopass->poller); + picopass_poller_free(picopass->poller); // Clear view popup_reset(picopass->popup); diff --git a/picopass/scenes/picopass_scene_write_key.c b/picopass/scenes/picopass_scene_write_key.c index 806a2b5a..842c1c84 100644 --- a/picopass/scenes/picopass_scene_write_key.c +++ b/picopass/scenes/picopass_scene_write_key.c @@ -1,10 +1,32 @@ #include "../picopass_i.h" #include -void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); +NfcCommand picopass_scene_write_key_poller_callback(PicopassPollerEvent event, void* context) { + NfcCommand command = NfcCommandContinue; Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); + + if(event.type == PicopassPollerEventTypeRequestMode) { + event.data->req_mode.mode = PicopassPollerModeWriteKey; + } else if(event.type == PicopassPollerEventTypeRequestKey) { + event.data->req_key.is_key_provided = true; + memcpy(event.data->req_key.key, picopass->dev->dev_data.pacs.key, PICOPASS_KEY_LEN); + event.data->req_key.is_elite_key = picopass->dev->dev_data.pacs.elite_kdf; + } else if(event.type == PicopassPollerEventTypeRequestWriteKey) { + event.data->req_write_key.data = &picopass->dev->dev_data; + memcpy( + event.data->req_write_key.key, + picopass->write_key_context.key_to_write, + PICOPASS_KEY_LEN); + event.data->req_write_key.is_elite_key = picopass->write_key_context.is_elite; + } else if(event.type == PicopassPollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerSuccess); + } else if(event.type == PicopassPollerEventTypeFail) { + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventPollerFail); + } + + return command; } void picopass_scene_write_key_on_enter(void* context) { @@ -18,14 +40,10 @@ void picopass_scene_write_key_on_enter(void* context) { // Start worker view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateWriteKey, - &picopass->dev->dev_data, - picopass_write_key_worker_callback, - picopass); - picopass_blink_start(picopass); + + picopass->poller = picopass_poller_alloc(picopass->nfc); + picopass_poller_start(picopass->poller, picopass_scene_write_key_poller_callback, picopass); } bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { @@ -33,9 +51,12 @@ bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventWorkerExit) { + if(event.event == PicopassCustomEventPollerSuccess) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); consumed = true; + } else if(event.event == PicopassCustomEventPollerFail) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure); + consumed = true; } } return consumed; @@ -45,7 +66,9 @@ void picopass_scene_write_key_on_exit(void* context) { Picopass* picopass = context; // Stop worker - picopass_worker_stop(picopass->worker); + picopass_poller_stop(picopass->poller); + picopass_poller_free(picopass->poller); + // Clear view popup_reset(picopass->popup); diff --git a/picopass/views/dict_attack.c b/picopass/views/dict_attack.c index fb7335f6..63df57c9 100644 --- a/picopass/views/dict_attack.c +++ b/picopass/views/dict_attack.c @@ -15,7 +15,6 @@ struct DictAttack { typedef struct { DictAttackState state; - MfClassicType type; FuriString* header; uint8_t sectors_total; uint8_t sectors_read; @@ -123,7 +122,6 @@ void dict_attack_reset(DictAttack* dict_attack) { DictAttackViewModel * model, { model->state = DictAttackStateRead; - model->type = MfClassicType1k; model->sectors_total = 1; model->sectors_read = 0; model->sector_current = 0; @@ -242,6 +240,15 @@ void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys true); } +void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->dict_keys_current = current_key; }, + true); +} + void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { furi_assert(dict_attack); with_view_model( diff --git a/picopass/views/dict_attack.h b/picopass/views/dict_attack.h index bdfa3e95..5f9872da 100644 --- a/picopass/views/dict_attack.h +++ b/picopass/views/dict_attack.h @@ -3,8 +3,6 @@ #include #include -#include - typedef struct DictAttack DictAttack; typedef void (*DictAttackCallback)(void* context); @@ -37,6 +35,8 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack); void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); +void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key); + void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); diff --git a/picopass/views/loclass.c b/picopass/views/loclass.c index 4257c251..4f9da2a4 100644 --- a/picopass/views/loclass.c +++ b/picopass/views/loclass.c @@ -1,5 +1,6 @@ #include "loclass.h" -#include "../picopass_worker_i.h" + +#include #include