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:
		
							parent
							
								
									f8e0ec42c5
								
							
						
					
					
						commit
						cd77b93f26
					
				| @ -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_comment_cstr(file, "Picopass blocks")) break; |  | ||||||
|                 // 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( |                     if(!flipper_format_write_hex( | ||||||
|                            file, |                            file, "PIN(cont.)\t", pacs->pin1, PICOPASS_BLOCK_LEN)) | ||||||
|                            string_get_cstr(temp_str), |                         break; | ||||||
|                            AA1->block[i].data, |                 } | ||||||
|                            PICOPASS_BLOCK_LEN)) { |             } | ||||||
|  |             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; |                     block_saved = false; | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if(!block_saved) break; |             if(!block_saved) break; | ||||||
|                 if(!flipper_format_write_comment_cstr(file, "This is currently incomplete")) 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; | ||||||
| } | } | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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"); | ||||||
|  | |||||||
| @ -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)); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts