Picopass: dump full card, extract some details (#1408)

* Dump entire picopass card
* Capture more iClass details
* facility code bugfix

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Eric Betts 2022-07-25 08:36:38 -07:00 committed by GitHub
parent f8e0ec42c5
commit cd77b93f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 57 deletions

View File

@ -10,6 +10,9 @@ static const uint32_t picopass_file_version = 1;
PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_device_alloc() {
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
picopass_dev->dev_data.pacs.legacy = false;
picopass_dev->dev_data.pacs.se_enabled = false;
picopass_dev->dev_data.pacs.pin_length = 0;
picopass_dev->storage = furi_record_open("storage"); picopass_dev->storage = furi_record_open("storage");
picopass_dev->dialogs = furi_record_open("dialogs"); picopass_dev->dialogs = furi_record_open("dialogs");
return picopass_dev; return picopass_dev;
@ -32,7 +35,7 @@ static bool picopass_device_save_file(
bool saved = false; bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage); FlipperFormat* file = flipper_format_file_alloc(dev->storage);
PicopassPacs* pacs = &dev->dev_data.pacs; PicopassPacs* pacs = &dev->dev_data.pacs;
ApplicationArea* AA1 = &dev->dev_data.AA1; PicopassBlock* AA1 = dev->dev_data.AA1;
string_t temp_str; string_t temp_str;
string_init(temp_str); string_init(temp_str);
@ -54,40 +57,40 @@ static bool picopass_device_save_file(
if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
if(dev->format == PicopassDeviceSaveFormatHF) { if(dev->format == PicopassDeviceSaveFormatHF) {
uint32_t fc = pacs->record.FacilityCode;
uint32_t cn = pacs->record.CardNumber;
// Write header // Write header
if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version)) if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
break; break;
if(pacs->record.valid) { if(pacs->record.valid) {
if(!flipper_format_write_uint32( if(!flipper_format_write_uint32(file, "Facility Code", &fc, 1)) break;
file, "Facility Code", (uint32_t*)&pacs->record.FacilityCode, 1)) if(!flipper_format_write_uint32(file, "Card Number", &cn, 1)) break;
break;
if(!flipper_format_write_uint32(
file, "Card Number", (uint32_t*)&pacs->record.CardNumber, 1))
break;
if(!flipper_format_write_hex( if(!flipper_format_write_hex(
file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN)) file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
break; break;
if(!flipper_format_write_hex(file, "PIN", pacs->pin0, PICOPASS_BLOCK_LEN)) break; if(pacs->pin_length > 0) {
if(!flipper_format_write_hex(file, "PIN(cont.)", pacs->pin1, PICOPASS_BLOCK_LEN)) if(!flipper_format_write_hex(file, "PIN\t\t", pacs->pin0, PICOPASS_BLOCK_LEN))
break; break;
if(!flipper_format_write_hex(
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break; file, "PIN(cont.)\t", pacs->pin1, PICOPASS_BLOCK_LEN))
// TODO: Save CSN, CFG, AA1, etc
bool block_saved = true;
for(size_t i = 0; i < 4; i++) {
string_printf(temp_str, "Block %d", i + 6);
if(!flipper_format_write_hex(
file,
string_get_cstr(temp_str),
AA1->block[i].data,
PICOPASS_BLOCK_LEN)) {
block_saved = false;
break; break;
}
} }
if(!block_saved) break;
if(!flipper_format_write_comment_cstr(file, "This is currently incomplete")) break;
} }
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break;
bool block_saved = true;
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 = 0; i < app_limit; i++) {
string_printf(temp_str, "Block %d", i);
if(!flipper_format_write_hex(
file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
block_saved = false;
break;
}
}
if(!block_saved) break;
} else if(dev->format == PicopassDeviceSaveFormatLF) { } else if(dev->format == PicopassDeviceSaveFormatLF) {
const char* lf_header = "Flipper RFID key"; const char* lf_header = "Flipper RFID key";
// Write header // Write header
@ -142,5 +145,10 @@ void picopass_device_free(PicopassDevice* picopass_dev) {
} }
void picopass_device_data_clear(PicopassDeviceData* dev_data) { void picopass_device_data_clear(PicopassDeviceData* dev_data) {
memset(&dev_data->AA1, 0, sizeof(ApplicationArea)); for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
}
dev_data->pacs.legacy = false;
dev_data->pacs.se_enabled = false;
dev_data->pacs.pin_length = 0;
} }

View File

@ -10,6 +10,11 @@
#define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_DEV_NAME_MAX_LEN 22
#define PICOPASS_READER_DATA_MAX_SIZE 64 #define PICOPASS_READER_DATA_MAX_SIZE 64
#define PICOPASS_BLOCK_LEN 8 #define PICOPASS_BLOCK_LEN 8
#define PICOPASS_MAX_APP_LIMIT 32
#define PICOPASS_CSN_BLOCK_INDEX 0
#define PICOPASS_CONFIG_BLOCK_INDEX 1
#define PICOPASS_AIA_BLOCK_INDEX 5
#define PICOPASS_APP_FOLDER "/any/picopass" #define PICOPASS_APP_FOLDER "/any/picopass"
#define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_EXTENSION ".picopass"
@ -35,7 +40,10 @@ typedef struct {
} PicopassWiegandRecord; } PicopassWiegandRecord;
typedef struct { typedef struct {
bool legacy;
bool se_enabled;
bool biometrics; bool biometrics;
uint8_t pin_length;
PicopassEncryption encryption; PicopassEncryption encryption;
uint8_t credential[8]; uint8_t credential[8];
uint8_t pin0[8]; uint8_t pin0[8];
@ -44,7 +52,11 @@ typedef struct {
} PicopassPacs; } PicopassPacs;
typedef struct { typedef struct {
ApplicationArea AA1; uint8_t data[PICOPASS_BLOCK_LEN];
} PicopassBlock;
typedef struct {
PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
PicopassPacs pacs; PicopassPacs pacs;
} PicopassDeviceData; } PicopassDeviceData;

View File

@ -55,12 +55,11 @@ static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRe
if(record->bitLength == 26) { if(record->bitLength == 26) {
uint8_t* v4 = data + 4; uint8_t* v4 = data + 4;
v4[0] = 0;
uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
record->CardNumber = (bot >> 1) & 0xFFFF; record->CardNumber = (bot >> 1) & 0xFFFF;
record->FacilityCode = (bot >> 17) & 0xFF; record->FacilityCode = (bot >> 17) & 0xFF;
FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber);
record->valid = true; record->valid = true;
} else { } else {
record->CardNumber = 0; record->CardNumber = 0;
@ -165,7 +164,7 @@ ReturnCode picopass_detect_card(int timeout) {
return ERR_NONE; return ERR_NONE;
} }
ReturnCode picopass_read_card(ApplicationArea* AA1) { ReturnCode picopass_read_card(PicopassBlock* AA1) {
rfalPicoPassIdentifyRes idRes; rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes; rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes; rfalPicoPassReadCheckRes rcRes;
@ -205,10 +204,20 @@ ReturnCode picopass_read_card(ApplicationArea* AA1) {
return err; return err;
} }
for(size_t i = 0; i < 4; i++) { rfalPicoPassReadBlockRes csn;
FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); err = rfalPicoPassPollerReadBlock(PICOPASS_CSN_BLOCK_INDEX, &csn);
memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn.data, sizeof(csn.data));
rfalPicoPassReadBlockRes cfg;
err = rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg);
memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data));
size_t app_limit = cfg.data[0] < PICOPASS_MAX_APP_LIMIT ? cfg.data[0] : PICOPASS_MAX_APP_LIMIT;
for(size_t i = 2; i < app_limit; i++) {
FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i);
rfalPicoPassReadBlockRes block; rfalPicoPassReadBlockRes block;
err = rfalPicoPassPollerReadBlock(i + 6, &block); err = rfalPicoPassPollerReadBlock(i, &block);
if(err != ERR_NONE) { if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
return err; return err;
@ -217,7 +226,7 @@ ReturnCode picopass_read_card(ApplicationArea* AA1) {
FURI_LOG_D( FURI_LOG_D(
TAG, TAG,
"rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x", "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
i + 6, i,
block.data[0], block.data[0],
block.data[1], block.data[1],
block.data[2], block.data[2],
@ -227,7 +236,7 @@ ReturnCode picopass_read_card(ApplicationArea* AA1) {
block.data[6], block.data[6],
block.data[7]); block.data[7]);
memcpy(&(AA1->block[i]), &block, sizeof(block)); memcpy(AA1[i].data, block.data, sizeof(block.data));
} }
return ERR_NONE; return ERR_NONE;
@ -251,7 +260,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
picopass_device_data_clear(picopass_worker->dev_data); picopass_device_data_clear(picopass_worker->dev_data);
PicopassDeviceData* dev_data = picopass_worker->dev_data; PicopassDeviceData* dev_data = picopass_worker->dev_data;
ApplicationArea* AA1 = &dev_data->AA1; PicopassBlock* AA1 = dev_data->AA1;
PicopassPacs* pacs = &dev_data->pacs; PicopassPacs* pacs = &dev_data->pacs;
ReturnCode err; ReturnCode err;
@ -263,34 +272,39 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
FURI_LOG_E(TAG, "picopass_read_card error %d", err); FURI_LOG_E(TAG, "picopass_read_card error %d", err);
} }
pacs->biometrics = AA1->block[0].data[4]; // Thank you proxmark!
pacs->encryption = AA1->block[0].data[7]; pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
if(pacs->encryption == 0x17) { 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"); FURI_LOG_D(TAG, "3DES Encrypted");
err = picopass_worker_decrypt(AA1->block[1].data, pacs->credential); err = picopass_worker_decrypt(AA1[7].data, pacs->credential);
if(err != ERR_NONE) { if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err); FURI_LOG_E(TAG, "decrypt error %d", err);
break; break;
} }
err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0); err = picopass_worker_decrypt(AA1[8].data, pacs->pin0);
if(err != ERR_NONE) { if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err); FURI_LOG_E(TAG, "decrypt error %d", err);
break; break;
} }
err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1); err = picopass_worker_decrypt(AA1[9].data, pacs->pin1);
if(err != ERR_NONE) { if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err); FURI_LOG_E(TAG, "decrypt error %d", err);
break; break;
} }
} else if(pacs->encryption == 0x14) { } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
FURI_LOG_D(TAG, "No Encryption"); FURI_LOG_D(TAG, "No Encryption");
memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN); memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
memcpy(pacs->pin0, AA1->block[2].data, RFAL_PICOPASS_MAX_BLOCK_LEN); memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
memcpy(pacs->pin1, AA1->block[3].data, RFAL_PICOPASS_MAX_BLOCK_LEN); memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
} else if(pacs->encryption == 0x15) { } else if(pacs->encryption == PicopassDeviceEncryptionDES) {
FURI_LOG_D(TAG, "DES Encrypted"); FURI_LOG_D(TAG, "DES Encrypted");
} else { } else {
FURI_LOG_D(TAG, "Unknown encryption"); FURI_LOG_D(TAG, "Unknown encryption");

View File

@ -29,14 +29,17 @@ void picopass_scene_read_card_success_on_enter(void* context) {
PicopassPacs* pacs = &picopass->dev->dev_data.pacs; PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget; Widget* widget = picopass->widget;
size_t bytesLength = 1 + pacs->record.bitLength / 8;
string_set_str(credential_str, ""); string_set_str(credential_str, "");
for(uint8_t i = 0; i < RFAL_PICOPASS_MAX_BLOCK_LEN; i++) { for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
string_cat_printf(credential_str, " %02X", pacs->credential[i]); string_cat_printf(credential_str, " %02X", pacs->credential[i]);
} }
if(pacs->record.valid) { if(pacs->record.valid) {
string_cat_printf( string_cat_printf(
wiegand_str, "FC: %03u CN: %05u", pacs->record.FacilityCode, pacs->record.CardNumber); wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
} else {
string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
} }
widget_add_button_element( widget_add_button_element(
@ -53,10 +56,8 @@ void picopass_scene_read_card_success_on_enter(void* context) {
picopass_scene_read_card_success_widget_callback, picopass_scene_read_card_success_widget_callback,
picopass); picopass);
if(pacs->record.valid) { widget_add_string_element(
widget_add_string_element( widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
}
widget_add_string_element( widget_add_string_element(
widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str));

View File

@ -51,10 +51,6 @@ typedef struct {
uint8_t crc[2]; uint8_t crc[2];
} rfalPicoPassReadBlockRes; } rfalPicoPassReadBlockRes;
typedef struct {
rfalPicoPassReadBlockRes block[4];
} ApplicationArea;
ReturnCode rfalPicoPassPollerInitialize(void); ReturnCode rfalPicoPassPollerInitialize(void);
ReturnCode rfalPicoPassPollerCheckPresence(void); ReturnCode rfalPicoPassPollerCheckPresence(void);
ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes);