Picopass: Read Elite (#1888)
* working elite dict * add csn to display Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									68009c6230
								
							
						
					
					
						commit
						56f760aa07
					
				
							
								
								
									
										151
									
								
								applications/plugins/picopass/helpers/iclass_elite_dict.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								applications/plugins/picopass/helpers/iclass_elite_dict.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| #include "iclass_elite_dict.h" | ||||
| 
 | ||||
| #include <lib/toolbox/args.h> | ||||
| #include <lib/flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt") | ||||
| #define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt") | ||||
| 
 | ||||
| #define TAG "IclassEliteDict" | ||||
| 
 | ||||
| #define ICLASS_ELITE_KEY_LINE_LEN (17) | ||||
| #define ICLASS_ELITE_KEY_LEN (8) | ||||
| 
 | ||||
| struct IclassEliteDict { | ||||
|     Stream* stream; | ||||
|     uint32_t total_keys; | ||||
| }; | ||||
| 
 | ||||
| bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
| 
 | ||||
|     bool dict_present = false; | ||||
|     if(dict_type == IclassEliteDictTypeFlipper) { | ||||
|         dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) == | ||||
|                        FSE_OK; | ||||
|     } else if(dict_type == IclassEliteDictTypeUser) { | ||||
|         dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK; | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     return dict_present; | ||||
| } | ||||
| 
 | ||||
| IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { | ||||
|     IclassEliteDict* dict = malloc(sizeof(IclassEliteDict)); | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     dict->stream = buffered_file_stream_alloc(storage); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
|     FuriString* next_line = furi_string_alloc(); | ||||
| 
 | ||||
|     bool dict_loaded = false; | ||||
|     do { | ||||
|         if(dict_type == IclassEliteDictTypeFlipper) { | ||||
|             if(!buffered_file_stream_open( | ||||
|                    dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|                 buffered_file_stream_close(dict->stream); | ||||
|                 break; | ||||
|             } | ||||
|         } else if(dict_type == IclassEliteDictTypeUser) { | ||||
|             if(!buffered_file_stream_open( | ||||
|                    dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|                 buffered_file_stream_close(dict->stream); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Read total amount of keys
 | ||||
|         while(true) { | ||||
|             if(!stream_read_line(dict->stream, next_line)) break; | ||||
|             if(furi_string_get_char(next_line, 0) == '#') continue; | ||||
|             if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; | ||||
|             dict->total_keys++; | ||||
|         } | ||||
|         furi_string_reset(next_line); | ||||
|         stream_rewind(dict->stream); | ||||
| 
 | ||||
|         dict_loaded = true; | ||||
|         FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!dict_loaded) { | ||||
|         buffered_file_stream_close(dict->stream); | ||||
|         free(dict); | ||||
|         dict = NULL; | ||||
|     } | ||||
| 
 | ||||
|     furi_string_free(next_line); | ||||
| 
 | ||||
|     return dict; | ||||
| } | ||||
| 
 | ||||
| void iclass_elite_dict_free(IclassEliteDict* dict) { | ||||
|     furi_assert(dict); | ||||
|     furi_assert(dict->stream); | ||||
| 
 | ||||
|     buffered_file_stream_close(dict->stream); | ||||
|     stream_free(dict->stream); | ||||
|     free(dict); | ||||
| } | ||||
| 
 | ||||
| uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict) { | ||||
|     furi_assert(dict); | ||||
| 
 | ||||
|     return dict->total_keys; | ||||
| } | ||||
| 
 | ||||
| bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key) { | ||||
|     furi_assert(dict); | ||||
|     furi_assert(dict->stream); | ||||
| 
 | ||||
|     uint8_t key_byte_tmp = 0; | ||||
|     FuriString* next_line = furi_string_alloc(); | ||||
| 
 | ||||
|     bool key_read = false; | ||||
|     *key = 0ULL; | ||||
|     while(!key_read) { | ||||
|         if(!stream_read_line(dict->stream, next_line)) break; | ||||
|         if(furi_string_get_char(next_line, 0) == '#') continue; | ||||
|         if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; | ||||
|         for(uint8_t i = 0; i < ICLASS_ELITE_KEY_LEN * 2; i += 2) { | ||||
|             args_char_to_hex( | ||||
|                 furi_string_get_char(next_line, i), | ||||
|                 furi_string_get_char(next_line, i + 1), | ||||
|                 &key_byte_tmp); | ||||
|             key[i / 2] = key_byte_tmp; | ||||
|         } | ||||
|         key_read = true; | ||||
|     } | ||||
| 
 | ||||
|     furi_string_free(next_line); | ||||
|     return key_read; | ||||
| } | ||||
| 
 | ||||
| bool iclass_elite_dict_rewind(IclassEliteDict* dict) { | ||||
|     furi_assert(dict); | ||||
|     furi_assert(dict->stream); | ||||
| 
 | ||||
|     return stream_rewind(dict->stream); | ||||
| } | ||||
| 
 | ||||
| bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) { | ||||
|     furi_assert(dict); | ||||
|     furi_assert(dict->stream); | ||||
| 
 | ||||
|     FuriString* key_str = furi_string_alloc(); | ||||
|     for(size_t i = 0; i < 6; i++) { | ||||
|         furi_string_cat_printf(key_str, "%02X", key[i]); | ||||
|     } | ||||
|     furi_string_cat_printf(key_str, "\n"); | ||||
| 
 | ||||
|     bool key_added = false; | ||||
|     do { | ||||
|         if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; | ||||
|         if(!stream_insert_string(dict->stream, key_str)) break; | ||||
|         key_added = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     furi_string_free(key_str); | ||||
|     return key_added; | ||||
| } | ||||
							
								
								
									
										28
									
								
								applications/plugins/picopass/helpers/iclass_elite_dict.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								applications/plugins/picopass/helpers/iclass_elite_dict.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <storage/storage.h> | ||||
| #include <lib/flipper_format/flipper_format.h> | ||||
| #include <lib/toolbox/stream/file_stream.h> | ||||
| #include <lib/toolbox/stream/buffered_file_stream.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     IclassEliteDictTypeUser, | ||||
|     IclassEliteDictTypeFlipper, | ||||
| } IclassEliteDictType; | ||||
| 
 | ||||
| typedef struct IclassEliteDict IclassEliteDict; | ||||
| 
 | ||||
| bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type); | ||||
| 
 | ||||
| IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type); | ||||
| 
 | ||||
| void iclass_elite_dict_free(IclassEliteDict* dict); | ||||
| 
 | ||||
| uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict); | ||||
| 
 | ||||
| bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key); | ||||
| 
 | ||||
| bool iclass_elite_dict_rewind(IclassEliteDict* dict); | ||||
| 
 | ||||
| bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); | ||||
| @ -185,7 +185,7 @@ static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8 | ||||
|  * @param loclass_hash1 loclass_hash1 | ||||
|  * @param key_sel output key_sel=h[loclass_hash1[i]] | ||||
|  */ | ||||
| void hash2(uint8_t* key64, uint8_t* outp_keytable) { | ||||
| void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable) { | ||||
|     /**
 | ||||
|      *Expected: | ||||
|      * High Security Key Table | ||||
|  | ||||
| @ -9,6 +9,7 @@ | ||||
| #include "rfal_picopass.h" | ||||
| #include <optimized_ikeys.h> | ||||
| #include <optimized_cipher.h> | ||||
| #include "helpers/iclass_elite_dict.h" | ||||
| 
 | ||||
| #define PICOPASS_DEV_NAME_MAX_LEN 22 | ||||
| #define PICOPASS_READER_DATA_MAX_SIZE 64 | ||||
| @ -49,6 +50,7 @@ typedef struct { | ||||
|     bool se_enabled; | ||||
|     bool sio; | ||||
|     bool biometrics; | ||||
|     uint8_t key[8]; | ||||
|     uint8_t pin_length; | ||||
|     PicopassEncryption encryption; | ||||
|     uint8_t credential[8]; | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| #include "picopass_worker_i.h" | ||||
| 
 | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #define TAG "PicopassWorker" | ||||
| 
 | ||||
| const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | ||||
| @ -176,7 +178,7 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { | ||||
|     return ERR_NONE; | ||||
| } | ||||
| 
 | ||||
| ReturnCode picopass_read_card(PicopassBlock* AA1) { | ||||
| ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||
|     rfalPicoPassReadCheckRes rcRes; | ||||
|     rfalPicoPassCheckRes chkRes; | ||||
| 
 | ||||
| @ -197,10 +199,68 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { | ||||
|     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; | ||||
|     if(err == ERR_NONE) { | ||||
|         return ERR_NONE; | ||||
|     } | ||||
|     FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Starting dictionary attack"); | ||||
| 
 | ||||
|     size_t index = 0; | ||||
|     uint8_t key[PICOPASS_BLOCK_LEN] = {0}; | ||||
| 
 | ||||
|     if(!iclass_elite_dict_check_presence(IclassEliteDictTypeFlipper)) { | ||||
|         FURI_LOG_E(TAG, "Dictionary not found"); | ||||
|         return ERR_PARAM; | ||||
|     } | ||||
| 
 | ||||
|     IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); | ||||
|     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 auth with key %d %02x%02x%02x%02x%02x%02x%02x%02x", | ||||
|             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); | ||||
|             return err; | ||||
|         } | ||||
|         memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||
| 
 | ||||
|         loclass_iclass_calc_div_key(AA1[PICOPASS_CSN_BLOCK_INDEX].data, key, div_key, true); | ||||
|         loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||
| 
 | ||||
|         err = rfalPicoPassPollerCheck(mac, &chkRes); | ||||
|         if(err == ERR_NONE) { | ||||
|             memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(dict) { | ||||
|         iclass_elite_dict_free(dict); | ||||
|     } | ||||
| 
 | ||||
|     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] : | ||||
| @ -352,28 +412,39 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | ||||
|             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; | ||||
|             } | ||||
| 
 | ||||
|             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_auth(AA1, pacs); | ||||
|                 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(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->record); | ||||
|             } | ||||
|             if(err != ERR_NONE) { | ||||
|                 FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); | ||||
|                 nextState = PicopassWorkerEventFail; | ||||
|                 if(err != ERR_NONE) { | ||||
|                     FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); | ||||
|                     nextState = PicopassWorkerEventFail; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Notify caller and exit
 | ||||
|  | ||||
| @ -18,6 +18,7 @@ | ||||
| struct PicopassWorker { | ||||
|     FuriThread* thread; | ||||
|     Storage* storage; | ||||
|     Stream* dict_stream; | ||||
| 
 | ||||
|     PicopassDeviceData* dev_data; | ||||
|     PicopassWorkerCallback callback; | ||||
|  | ||||
| @ -15,12 +15,10 @@ void picopass_scene_read_card_success_widget_callback( | ||||
| 
 | ||||
| void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     FuriString* credential_str; | ||||
|     FuriString* wiegand_str; | ||||
|     FuriString* sio_str; | ||||
|     credential_str = furi_string_alloc(); | ||||
|     wiegand_str = furi_string_alloc(); | ||||
|     sio_str = furi_string_alloc(); | ||||
|     FuriString* csn_str = furi_string_alloc_set("CSN:"); | ||||
|     FuriString* credential_str = furi_string_alloc(); | ||||
|     FuriString* wiegand_str = furi_string_alloc(); | ||||
|     FuriString* sio_str = furi_string_alloc(); | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||
| 
 | ||||
| @ -28,10 +26,18 @@ void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|     notification_message(picopass->notifications, &sequence_success); | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     PicopassBlock* AA1 = picopass->dev->dev_data.AA1; | ||||
|     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; | ||||
|     Widget* widget = picopass->widget; | ||||
| 
 | ||||
|     if(pacs->record.bitLength == 0) { | ||||
|     uint8_t csn[PICOPASS_BLOCK_LEN]; | ||||
|     memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); | ||||
|     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||
|         furi_string_cat_printf(csn_str, " %02X", csn[i]); | ||||
|     } | ||||
| 
 | ||||
|     // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 | ||||
|     if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { | ||||
|         furi_string_cat_printf(wiegand_str, "Read Failed"); | ||||
| 
 | ||||
|         if(pacs->se_enabled) { | ||||
| @ -79,18 +85,21 @@ void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|     } | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); | ||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); | ||||
|     widget_add_string_element( | ||||
|         widget, | ||||
|         64, | ||||
|         32, | ||||
|         36, | ||||
|         AlignCenter, | ||||
|         AlignCenter, | ||||
|         FontSecondary, | ||||
|         furi_string_get_cstr(credential_str)); | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); | ||||
|         widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); | ||||
| 
 | ||||
|     furi_string_free(csn_str); | ||||
|     furi_string_free(credential_str); | ||||
|     furi_string_free(wiegand_str); | ||||
|     furi_string_free(sio_str); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Patrick Cunningham
						Patrick Cunningham