diff --git a/CHANGELOG.md b/CHANGELOG.md index ac9cbf8c5..bab67b77d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Added: - Apps: - Games: Checkers (by @H4W9) + - GPIO: CAN Commander (by @MatthewKuKanich) - NFC: - ISO 15693-3 NFC Writer (by @ch4istO) - UL-C Bruteforce (by @noproto) @@ -14,6 +15,7 @@ - UL: Jarolift protocol full support (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) - UL: Treadmill37 protocol support (37bit static) (by @xMasterX) - UL: Ditec GOL4 protocol (with programming mode, button switch, add manually) (by @xMasterX & @zero-mega) + - UL: KeyFinder protocol (24bit static) (by @xMasterX & @mishamyte) - UL: New modulation FSK with 12KHz deviation (by @xMasterX) - UL: KingGates Stylo 4k Add manually and button switch support and refactoring of encoder (by @xMasterX) - UL: Stilmatic (R-Tech) 12bit discr. fix & button 9 support (two buttons hold simulation) (mapped on arrow keys) (by @xMasterX) @@ -25,6 +27,8 @@ - UL: TX Power setting (by @LeeroysHub) - UL: Somfy Keytis button switch and Add Manually support (by @xMasterX) - UL: Genius Echo/Bravo add 2 buttons hold simulation (0xB btn code) (by @xMasterX) +- NFC: Add Mifare Ultralight C Write Support (#524 by @haw8411) +- OFW: RFID: Add Indala 224-bit (long format) protocol support (by @kuzaxak) - UL: JS: Add IR capabilities to the JS engine (by @LuisMayo) - FBT: Allow apps to specify custom cflags (by @WillyJL) - UL: Docs: Add [full list of supported SubGHz protocols](https://github.com/Next-Flip/Momentum-Firmware/blob/dev/documentation/SubGHzSupportedSystems.md) and their frequencies/modulations that can be used for reading remotes (by @xMasterX) @@ -39,14 +43,16 @@ - FlipLibrary: Added Fahrenheit, current weather, and wind speed/direction (by @H4W9) - FlipSocial: Autocomplete, keyboard improvements, explore and profile view enhancements, bugfixes (by @jblanked) - FlipWeather: Added Fahrenheit, current weather, and wind speed/direction (by @H4W9) + - Free Roam: Minimap, code optimization, ux improvements (by @jblanked) - Flipper Blackhat: TUI command (by @o7-machinehum) - Geometry Dash: Major refactor, bugfixes and performance improvements, rename from Geometry Flip (by @gooseprjkt) - HC-SR04 Distance Sensor: Option to change measure units (by @Tyl3rA) - IconEdit: Save/Send animations, settings tab with canvas scale and cursor guides, bugfixes (by @rdefeo) - INA2xx INA Meter: Fixed application freezing when the sensor is not connected (by @cepetr) + - Lidar Emulator: Support external IR boards (by @ANTodorov) - NFC Login: Code refactor, bugfixes, renamed from NFC PC Login, restore BLE profile on app exit (by @Play2BReal) - - Picopass: Option to Create credential without a card (by @redteamlife) - - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse) + - Picopass: Option to Create credential without a card (by @redteamlife), info screen improvements, allow Emulation of NRMAC dumped cards (by @bettse) + - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse), reverse engineered U90 packet, memory optimization (by @cindersocket) - Seos Compatible: Seos write support, various improvements (by @aaronjamt), support switching key sets (by @pcunning), code refactoring, various bugfixes (by @bettse) - Sub-GHz Scheduler: Added new interval times, bugfixes and improvements (by @shalebridge) - Tetris: Various bugfixes (by @Bricktech2000) @@ -64,6 +70,7 @@ - UL: Signal Settings Improvements (by @Dmitry422) - UL: KeeLoq change delta size (by @xMasterX) - Archive: Support opening and pinning ProtoPirate files from Archive (#510 by @LeeroysHub) +- OFW: RFID: Make FDX-B readout more descriptive (by @snowsign) - OFW: API: Make `view_port_send_to_back()` public (by @loftyinclination) ### Fixed: diff --git a/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c index 032fe115e..f39b8e2ee 100644 --- a/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c +++ b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c @@ -526,6 +526,134 @@ MU_TEST(test_lfrfid_protocol_fdxb_read_simple) { protocol_dict_free(dict); } +// Indala224: 224-bit PSK2 frame, no known FC/CN descramble, +// test data uses the Proxmark3-verified reference (lf indala reader output) +#define INDALA224_TEST_DATA_SIZE 28 +#define INDALA224_TEST_DATA \ + {0x80, 0x00, 0x00, 0x01, 0xB2, 0x35, 0x23, 0xA6, 0xC2, 0xE3, 0x1E, 0xBA, 0x3C, 0xBE, \ + 0xE4, 0xAF, 0xB3, 0xC6, 0xAD, 0x1F, 0xCF, 0x64, 0x93, 0x93, 0x92, 0x8C, 0x14, 0xE5} +#define INDALA224_BITS_PER_FRAME 224 +#define INDALA224_US_PER_BIT 255 +#define INDALA224_FRAMES_TO_DECODE 3 + +MU_TEST(test_lfrfid_protocol_indala224_roundtrip) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + INDALA224_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIndala224)); + mu_assert_string_eq("Indala224", protocol_dict_get_name(dict, LFRFIDProtocolIndala224)); + mu_assert_string_eq("Motorola", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIndala224)); + + const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_TEST_DATA; + + // Encode: extract bit-level polarity from encoder output. + // PSK encoder yields carrier-level oscillation (duration=1 per half-cycle). + // PulseGlue produces ~16us outputs which are below the decoder's 127us threshold, + // so we extract the bit-level polarity directly: sample the encoder's phase state + // at the start of each bit period (every 32 yields = 16 carrier cycles). + protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224)); + + const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE; + bool* bit_levels = malloc(total_bits); + mu_check(bit_levels != NULL); + + for(size_t i = 0; i < total_bits; i++) { + LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224); + bit_levels[i] = level_duration_get_level(ld); + // Skip remaining 31 yields for this bit period + for(size_t skip = 0; skip < 31; skip++) { + protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224); + } + } + + // Decode: convert bit-level polarities to run-length timing pairs + // and feed directly to decoder (simulates hardware PSK demodulator output). + protocol_dict_decoders_start(dict); + ProtocolId protocol = PROTOCOL_NO; + + size_t i = 0; + while(i < total_bits) { + bool cur = bit_levels[i]; + size_t run = 1; + while(i + run < total_bits && bit_levels[i + run] == cur) { + run++; + } + uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT); + + protocol = protocol_dict_decoders_feed(dict, cur, duration); + if(protocol != PROTOCOL_NO) break; + + i += run; + } + + free(bit_levels); + + mu_assert_int_eq(LFRFIDProtocolIndala224, protocol); + uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE); + mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + +// Indala224 phase-alternating test: data with odd number of 1-bits +// causes PSK2 carrier phase to invert between consecutive frames. +// Decoder must accept inverted preamble at the second frame boundary. +#define INDALA224_ALT_TEST_DATA \ + {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + +MU_TEST(test_lfrfid_protocol_indala224_alternating_phase) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + + const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_ALT_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224)); + + const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE; + bool* bit_levels = malloc(total_bits); + mu_check(bit_levels != NULL); + + for(size_t i = 0; i < total_bits; i++) { + LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224); + bit_levels[i] = level_duration_get_level(ld); + for(size_t skip = 0; skip < 31; skip++) { + protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224); + } + } + + // Verify phase alternation: frame 1 and frame 2 start with opposite polarity + mu_check(bit_levels[0] != bit_levels[INDALA224_BITS_PER_FRAME]); + + protocol_dict_decoders_start(dict); + ProtocolId protocol = PROTOCOL_NO; + + size_t i = 0; + while(i < total_bits) { + bool cur = bit_levels[i]; + size_t run = 1; + while(i + run < total_bits && bit_levels[i + run] == cur) { + run++; + } + uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT); + + protocol = protocol_dict_decoders_feed(dict, cur, duration); + if(protocol != PROTOCOL_NO) break; + + i += run; + } + + free(bit_levels); + + mu_assert_int_eq(LFRFIDProtocolIndala224, protocol); + uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE); + mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + MU_TEST_SUITE(test_lfrfid_protocols_suite) { MU_RUN_TEST(test_lfrfid_protocol_em_read_simple); MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple); @@ -538,6 +666,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) { MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple); + MU_RUN_TEST(test_lfrfid_protocol_indala224_roundtrip); + MU_RUN_TEST(test_lfrfid_protocol_indala224_alternating_phase); + MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple); MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple); } diff --git a/applications/external b/applications/external index 8c980b149..805c3e7fd 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 8c980b1498ecfdfe9995e1d39b644235680ecc47 +Subproject commit 805c3e7fda279ac41e06b1c82e127db0f99c32b6 diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 5a0c88cf7..642f9b460 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -14,7 +14,9 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, - SubmenuIndexDictAttack + SubmenuIndexDictAttack, + SubmenuIndexWriteKeepKey, // ULC: write data pages, keep target card's existing key + SubmenuIndexWriteCopyKey, // ULC: write all pages including key from source card }; enum { @@ -214,8 +216,26 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc if(is_locked || (data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 && data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 && - data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) { + data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin && + data->type != MfUltralightTypeMfulC)) { submenu_remove_item(submenu, SubmenuIndexCommonWrite); + } else if(data->type == MfUltralightTypeMfulC) { + // Replace the generic Write item with two ULC-specific options so the user + // can choose whether to keep or overwrite the target card's 3DES key. + // This avoids any mid-write dialog/view-switching complexity entirely. + submenu_remove_item(submenu, SubmenuIndexCommonWrite); + submenu_add_item( + submenu, + "Write (Keep Key)", + SubmenuIndexWriteKeepKey, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Write (Copy Key)", + SubmenuIndexWriteCopyKey, + nfc_protocol_support_common_submenu_callback, + instance); } if(is_locked) { @@ -291,6 +311,14 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); } consumed = true; + } else if(event.event == SubmenuIndexWriteKeepKey) { + instance->mf_ultralight_c_write_context.copy_key = false; + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; + } else if(event.event == SubmenuIndexWriteCopyKey) { + instance->mf_ultralight_c_write_context.copy_key = true; + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; } } return consumed; @@ -307,12 +335,139 @@ static NfcCommand if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) { mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite; furi_string_reset(instance->text_box_store); + if(instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + } + instance->mf_ultralight_c_dict_context.dict = NULL; + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle; view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + // Skip auth during the read phase of write - we'll authenticate + // against the target card in RequestWriteData using source key or dict attack mf_ultralight_event->data->auth_context.skip_auth = true; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestKey) { + // Dict attack key provider - user dict first, then system dict + if(!instance->mf_ultralight_c_dict_context.dict && + instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictIdle) { + if(keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictUser; + } + if(!instance->mf_ultralight_c_dict_context.dict) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + instance->mf_ultralight_c_write_context.dict_state = + NfcMfUltralightCWriteDictSystem; + } + } + MfUltralightC3DesAuthKey key = {}; + bool got_key = false; + if(instance->mf_ultralight_c_dict_context.dict) { + got_key = keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey)); + } + if(!got_key && + instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictUser) { + // Exhausted user dict, switch to system dict + if(instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictSystem; + if(instance->mf_ultralight_c_dict_context.dict) { + got_key = keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey)); + } + } + if(got_key) { + mf_ultralight_event->data->key_request_data.key = key; + mf_ultralight_event->data->key_request_data.key_provided = true; + FURI_LOG_D( + "MfULC", + "Trying dict key: " + "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + key.data[0], + key.data[1], + key.data[2], + key.data[3], + key.data[4], + key.data[5], + key.data[6], + key.data[7], + key.data[8], + key.data[9], + key.data[10], + key.data[11], + key.data[12], + key.data[13], + key.data[14], + key.data[15]); + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + FURI_LOG_D("MfULC", "Dict exhausted - no more keys"); + if(instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; + } + instance->mf_ultralight_c_write_context.dict_state = + NfcMfUltralightCWriteDictExhausted; + } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) { mf_ultralight_event->data->write_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + // Reset dict context so RequestKey starts fresh for the write-phase auth + if(instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; + } + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteKeyRequest) { + // Apply the user's key choice - read from static, not scene state (scene manager + // resets state to 0 on scene entry, wiping any value set before next_scene). + bool keep_key = !instance->mf_ultralight_c_write_context.copy_key; + mf_ultralight_event->data->write_key_skip = keep_key; + + if(mf_ultralight_event->data->key_request_data.key_provided) { + MfUltralightC3DesAuthKey found_key = mf_ultralight_event->data->key_request_data.key; + FURI_LOG_D( + "MfULC", + "WriteKeyRequest: target key = " + "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + found_key.data[0], + found_key.data[1], + found_key.data[2], + found_key.data[3], + found_key.data[4], + found_key.data[5], + found_key.data[6], + found_key.data[7], + found_key.data[8], + found_key.data[9], + found_key.data[10], + found_key.data[11], + found_key.data[12], + found_key.data[13], + found_key.data[14], + found_key.data[15]); + } + FURI_LOG_D( + "MfULC", + "WriteKeyRequest: decision = %s (copy_key=%d)", + keep_key ? "KEEP target key (pages 44-47 NOT written)" : + "OVERWRITE with source key (pages 44-47 WILL be written)", + (int)instance->mf_ultralight_c_write_context.copy_key); } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) { furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented"); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); @@ -323,6 +478,7 @@ static NfcCommand view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); command = NfcCommandStop; } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); command = NfcCommandStop; } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) { furi_string_reset(instance->text_box_store); @@ -334,9 +490,18 @@ static NfcCommand } static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) { + // Free any dict the write callback opened (dict_state != Idle means we own it). + // After a DictAttack scene, on_exit now NULLs the pointer so a simple NULL check + // is safe here too — but the state enum is the authoritative ownership record. + if(instance->mf_ultralight_c_write_context.dict_state != NfcMfUltralightCWriteDictIdle && + instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + } + instance->mf_ultralight_c_dict_context.dict = NULL; + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle; + furi_string_set(instance->text_box_store, "\nApply the\ntarget\ncard now"); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance); - furi_string_set(instance->text_box_store, "Apply the initial\ncard only"); } const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index c03415cdd..5a84a9acc 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -126,6 +126,18 @@ typedef struct { size_t dict_keys_current; } NfcMfUltralightCDictContext; +typedef enum { + NfcMfUltralightCWriteDictIdle, /**< No dict open; safe to open either dict. */ + NfcMfUltralightCWriteDictUser, /**< User dict currently open. */ + NfcMfUltralightCWriteDictSystem, /**< System dict currently open. */ + NfcMfUltralightCWriteDictExhausted, /**< All dicts tried; do not re-open. */ +} NfcMfUltralightCWriteDictState; + +typedef struct { + bool copy_key; /**< True = overwrite target 3DES key with source key pages. */ + NfcMfUltralightCWriteDictState dict_state; /**< Which dict is open for write-phase auth. */ +} NfcMfUltralightCWriteContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -165,6 +177,7 @@ struct NfcApp { SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; NfcMfUltralightCDictContext mf_ultralight_c_dict_context; + NfcMfUltralightCWriteContext mf_ultralight_c_write_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 843261142..ed9307759 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -77,6 +77,15 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { // Set attack type to Ultralight C dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + // Guard: if a previous write phase left a dict handle open, close it now. + // Without this, navigating write->back->read->dict-attack would open the same + // file twice, corrupting VFS state and causing a ViewPort lockup. + if(instance->mf_ultralight_c_dict_context.dict) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; + instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle; + } + if(state == DictAttackStateUserDictInProgress) { do { if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { @@ -167,6 +176,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; scene_manager_set_scene_state( instance->scene_manager, NfcSceneMfUltralightCDictAttack, @@ -199,6 +209,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; scene_manager_set_scene_state( instance->scene_manager, NfcSceneMfUltralightCDictAttack, @@ -230,6 +241,7 @@ void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { NfcSceneMfUltralightCDictAttack, DictAttackStateUserDictInProgress); keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict = NULL; instance->mf_ultralight_c_dict_context.dict_keys_total = 0; instance->mf_ultralight_c_dict_context.dict_keys_current = 0; instance->mf_ultralight_c_dict_context.auth_success = false; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 95c2de162..ad012e6c4 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -147,19 +147,19 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } //CC1101 Stop RX -> Start TX subghz_txrx_hopper_pause(subghz->txrx); + // key concept: we start endless TX until user release OK button, and after this we send last + // protocols repeats - this guarantee that one press OK will + // be guarantee send the required minimum protocol data packets + // for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function. + subghz->state_notifications = SubGhzNotificationStateTx; + subghz_block_generic_global.endless_tx = true; if(!subghz_tx_start( subghz, subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) { subghz_txrx_rx_start(subghz->txrx); subghz_txrx_hopper_unpause(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; - } else { - // key concept: we start endless TX until user release OK button, and after this we send last - // protocols repeats - this guarantee that one press OK will - // be guarantee send the required minimum protocol data packets - // for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function. - subghz->state_notifications = SubGhzNotificationStateTx; - subghz_block_generic_global.endless_tx = true; + subghz_block_generic_global.endless_tx = false; } return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 695d47860..a5b9de700 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -86,6 +86,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - SMC5326 `315MHz, 433.92MHz, Any other frequency` `AM650` (25 bits, Static) - Hay21 `433.92MHz` `AM650` (21 bits, Dynamic) - Treadmill37 (QH-433) `433.92MHz` `AM650` (37 bits, Static) +- KeyFinder `433.92MHz` `AM650` (24 bits, Static) --- diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index cbb1e15a3..2873ea6b0 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -4,6 +4,7 @@ #include "protocol_h10301.h" #include "protocol_idteck.h" #include "protocol_indala26.h" +#include "protocol_indala224.h" #include "protocol_io_prox_xsf.h" #include "protocol_awid.h" #include "protocol_fdx_a.h" @@ -31,6 +32,7 @@ const ProtocolBase* const lfrfid_protocols[] = { [LFRFIDProtocolH10301] = &protocol_h10301, [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, + [LFRFIDProtocolIndala224] = &protocol_indala224, [LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf, [LFRFIDProtocolAwid] = &protocol_awid, [LFRFIDProtocolFDXA] = &protocol_fdx_a, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index d6e13c2e4..14a6bdad2 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -16,6 +16,7 @@ typedef enum { LFRFIDProtocolH10301, LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, + LFRFIDProtocolIndala224, LFRFIDProtocolIOProxXSF, LFRFIDProtocolAwid, LFRFIDProtocolFDXA, diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index afec0672a..6c13233df 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -284,9 +284,12 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { bool block_status = bit_lib_get_bit(protocol->data, 48); bool rudi_bit = bit_lib_get_bit(protocol->data, 49); - uint8_t reserved = bit_lib_get_bits(protocol->data, 50, 5); - uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5); - uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3); + uint8_t visual_start_digit = + bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 50, 3) << 5); + uint8_t reserved = bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 53, 2) << 6); + uint8_t user_info = bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 55, 5) << 3); + uint8_t replacement_number = + bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 60, 3) << 5); bool animal_flag = bit_lib_get_bit(protocol->data, 63); Storage* storage = furi_record_open(RECORD_STORAGE); FuriString* country_full_name = furi_string_alloc(); @@ -320,13 +323,19 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { result, "\n" "Animal: %s\n" - "Bits: %hhX-%hhX-%hhX-%hhX-%hhX", + "Visual Start Digit: %hu\n" + "Replacement Number: %hu\n" + "User Info: %hhX\n" + "Data Block: %s\n" + "RUDI Bit: %s\n" + "RFU: %hhX\n", animal_flag ? "Yes" : "No", - block_status, - rudi_bit, - reserved, + visual_start_digit, + replacement_number, user_info, - replacement_number); + block_status ? "Present" : "Absent", + rudi_bit ? "Yes" : "No", + reserved); furi_string_free(country_full_name); } diff --git a/lib/lfrfid/protocols/protocol_indala224.c b/lib/lfrfid/protocols/protocol_indala224.c new file mode 100644 index 000000000..553509669 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala224.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +#define INDALA224_PREAMBLE_BIT_SIZE (30) +#define INDALA224_PREAMBLE_DATA_SIZE (4) + +#define INDALA224_ENCODED_BIT_SIZE (224) +#define INDALA224_ENCODED_DATA_SIZE \ + (((INDALA224_ENCODED_BIT_SIZE) / 8) + INDALA224_PREAMBLE_DATA_SIZE) +#define INDALA224_ENCODED_DATA_LAST ((INDALA224_ENCODED_BIT_SIZE) / 8) + +#define INDALA224_DECODED_BIT_SIZE (224) +#define INDALA224_DECODED_DATA_SIZE (28) + +#define INDALA224_US_PER_BIT (255) +#define INDALA224_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool current_polarity; + bool pulse_phase; +} ProtocolIndala224Encoder; + +typedef struct { + uint8_t encoded_data[INDALA224_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[INDALA224_ENCODED_DATA_SIZE]; + + uint8_t data[INDALA224_DECODED_DATA_SIZE]; + ProtocolIndala224Encoder encoder; +} ProtocolIndala224; + +ProtocolIndala224* protocol_indala224_alloc(void) { + ProtocolIndala224* protocol = malloc(sizeof(ProtocolIndala224)); + return protocol; +} + +void protocol_indala224_free(ProtocolIndala224* protocol) { + free(protocol); +} + +uint8_t* protocol_indala224_get_data(ProtocolIndala224* protocol) { + return protocol->data; +} + +void protocol_indala224_decoder_start(ProtocolIndala224* protocol) { + memset(protocol->encoded_data, 0, INDALA224_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, INDALA224_ENCODED_DATA_SIZE); +} + +static bool protocol_indala224_check_preamble(uint8_t* data, size_t bit_index) { + // Normal preamble: 1 followed by 29 zeros + if(bit_lib_get_bits(data, bit_index, 8) != 0b10000000) return false; + if(bit_lib_get_bits(data, bit_index + 8, 8) != 0) return false; + if(bit_lib_get_bits(data, bit_index + 16, 8) != 0) return false; + if(bit_lib_get_bits(data, bit_index + 24, 6) != 0) return false; + return true; +} + +static bool protocol_indala224_check_inverted_preamble(uint8_t* data, size_t bit_index) { + // Inverted preamble: 0 followed by 29 ones (phase-alternating PSK2 cards) + if(bit_lib_get_bits(data, bit_index, 8) != 0b01111111) return false; + if(bit_lib_get_bits(data, bit_index + 8, 8) != 0xFF) return false; + if(bit_lib_get_bits(data, bit_index + 16, 8) != 0xFF) return false; + if(bit_lib_get_bits(data, bit_index + 24, 6) != 0b111111) return false; + return true; +} + +static bool protocol_indala224_can_be_decoded(uint8_t* data) { + if(!protocol_indala224_check_preamble(data, 0)) return false; + // Second frame may have same or inverted preamble (PSK2 phase alternation) + if(!protocol_indala224_check_preamble(data, INDALA224_ENCODED_BIT_SIZE) && + !protocol_indala224_check_inverted_preamble(data, INDALA224_ENCODED_BIT_SIZE)) { + return false; + } + return true; +} + +static bool protocol_indala224_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (INDALA224_US_PER_BIT / 2); + + size_t bit_count = (time / INDALA224_US_PER_BIT); + bool result = false; + + if(bit_count < INDALA224_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, INDALA224_ENCODED_DATA_SIZE, polarity); + if(protocol_indala224_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_indala224_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + // PSK2 differential decode: the shift register contains phase values, not data. + // T5577 PSK2 encodes data as phase transitions: data[n] = phase[n] XOR phase[n+1]. + // Bit 224 (start of second frame) provides phase[n+1] for the last data bit. + for(size_t i = 0; i < INDALA224_DECODED_BIT_SIZE; i++) { + bool phase_current = bit_lib_get_bit(data_from, i); + bool phase_next = bit_lib_get_bit(data_from, i + 1); + bit_lib_set_bit(data_to, i, phase_current ^ phase_next); + } +} + +bool protocol_indala224_decoder_feed(ProtocolIndala224* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (INDALA224_US_PER_BIT / 2)) { + if(protocol_indala224_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_indala224_decoder_save(protocol->data, protocol->encoded_data); + FURI_LOG_D("Indala224", "Positive"); + result = true; + return result; + } + + if(protocol_indala224_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_indala224_decoder_save(protocol->data, protocol->negative_encoded_data); + FURI_LOG_D("Indala224", "Negative"); + result = true; + return result; + } + } + + return result; +} + +bool protocol_indala224_encoder_start(ProtocolIndala224* protocol) { + // Store raw data for PSK2 emulation - the yield function handles PSK2 modulation. + memcpy(protocol->encoded_data, protocol->data, INDALA224_DECODED_DATA_SIZE); + + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +} + +LevelDuration protocol_indala224_encoder_yield(ProtocolIndala224* protocol) { + LevelDuration level_duration; + ProtocolIndala224Encoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= INDALA224_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + // PSK2: carrier phase flips when data bit is 1 + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + if(current_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + bit_lib_increment_index(encoder->data_index, INDALA224_ENCODED_BIT_SIZE); + } + } + + return level_duration; +} + +static void protocol_indala224_render_data_internal( + ProtocolIndala224* protocol, + FuriString* result, + bool brief) { + if(brief) { + furi_string_printf( + result, + "Raw: %02X%02X%02X%02X\n" + " %02X%02X%02X%02X...", + protocol->data[0], + protocol->data[1], + protocol->data[2], + protocol->data[3], + protocol->data[4], + protocol->data[5], + protocol->data[6], + protocol->data[7]); + } else { + furi_string_printf( + result, + "Raw: %02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X", + protocol->data[0], + protocol->data[1], + protocol->data[2], + protocol->data[3], + protocol->data[4], + protocol->data[5], + protocol->data[6], + protocol->data[7], + protocol->data[8], + protocol->data[9], + protocol->data[10], + protocol->data[11], + protocol->data[12], + protocol->data[13], + protocol->data[14], + protocol->data[15], + protocol->data[16], + protocol->data[17], + protocol->data[18], + protocol->data[19], + protocol->data[20], + protocol->data[21], + protocol->data[22], + protocol->data[23], + protocol->data[24], + protocol->data[25], + protocol->data[26], + protocol->data[27]); + } +} + +void protocol_indala224_render_data(ProtocolIndala224* protocol, FuriString* result) { + protocol_indala224_render_data_internal(protocol, result, false); +} + +void protocol_indala224_render_brief_data(ProtocolIndala224* protocol, FuriString* result) { + protocol_indala224_render_data_internal(protocol, result, true); +} + +bool protocol_indala224_write_data(ProtocolIndala224* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + if(request->write_type == LFRFIDWriteTypeT5577) { + // Config: PSK2, RF/32, 7 data blocks (matches Proxmark3 T55X7_INDALA_224_CONFIG_BLOCK) + // T5577 data blocks contain raw data bits; the chip handles PSK2 modulation. + request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK2 | + (7 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->data, 96, 32); + request->t5577.block[5] = bit_lib_get_bits_32(protocol->data, 128, 32); + request->t5577.block[6] = bit_lib_get_bits_32(protocol->data, 160, 32); + request->t5577.block[7] = bit_lib_get_bits_32(protocol->data, 192, 32); + request->t5577.blocks_to_write = 8; + result = true; + } + return result; +} + +const ProtocolBase protocol_indala224 = { + .name = "Indala224", + .manufacturer = "Motorola", + .data_size = INDALA224_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_indala224_alloc, + .free = (ProtocolFree)protocol_indala224_free, + .get_data = (ProtocolGetData)protocol_indala224_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_indala224_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_indala224_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_indala224_encoder_start, + .yield = (ProtocolEncoderYield)protocol_indala224_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_indala224_render_data, + .render_brief_data = (ProtocolRenderData)protocol_indala224_render_brief_data, + .write_data = (ProtocolWriteData)protocol_indala224_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_indala224.h b/lib/lfrfid/protocols/protocol_indala224.h new file mode 100644 index 000000000..de23d8034 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala224.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_indala224; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index d1f08294f..8abfb05d2 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -575,13 +575,15 @@ uint8_t mf_ultralight_get_write_end_page(MfUltralightType type) { furi_assert( type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21 || type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || - type == MfUltralightTypeNTAG216 || type == MfUltralightTypeOrigin); + type == MfUltralightTypeNTAG216 || type == MfUltralightTypeOrigin || + type == MfUltralightTypeMfulC); uint8_t end_page = mf_ultralight_get_config_page_num(type); if(type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || type == MfUltralightTypeNTAG216) { end_page -= 1; - } else if(type == MfUltralightTypeOrigin) { + } else if(type == MfUltralightTypeOrigin || type == MfUltralightTypeMfulC) { + // ULC: 48 pages total, write pages 4-47 (includes auth config + 3DES key) end_page = mf_ultralight_features[type].total_pages; } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c index 050f9abc1..d8c5fb1e6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -186,7 +186,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_T(TAG, "CMD_WRITE"); + FURI_LOG_T(TAG, "CMD_WRITE page %d", start_page); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -197,12 +197,22 @@ static MfUltralightCommand break; } - if(!mf_ultralight_listener_check_access( - instance, start_page, MfUltralightListenerAccessTypeWrite)) - break; + // PATCHED: For Ultralight-C, allow writes to pages 44-47 (3DES key area) + // This enables "magic card" emulation for key grabbing + bool is_ulc_key_page = (instance->data->type == MfUltralightTypeMfulC) && + (start_page >= 44 && start_page <= 47); - if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; - if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + if(!is_ulc_key_page) { + // Normal access check for all other pages + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) + break; + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; + if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + } else { + FURI_LOG_I(TAG, "MAGIC: Allowing write to ULC key page %d", start_page); + } const uint8_t* rx_data = bit_buffer_get_data(buffer); command = diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 80518b1a8..6dd761af8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -698,9 +698,12 @@ static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPo instance->mfu_event.type = MfUltralightPollerEventTypeRequestWriteData; instance->callback(instance->general_event, instance->context); - const MfUltralightData* write_data = instance->mfu_event.data->write_data; + // Save write_data to instance field before any further events clobber the union + instance->write_data = instance->mfu_event.data->write_data; + const MfUltralightData* write_data = instance->write_data; const MfUltralightData* tag_data = instance->data; uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + instance->write_skip_key = false; bool check_passed = false; do { @@ -738,6 +741,71 @@ static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPo check_passed = true; } while(false); + // ULC: authenticate the target card before writing. + // The read phase left the card unauthenticated (write-mode callback skips read-phase auth). + // Ask callback for keys (cache first, then dict) until one authenticates, then fire + // WriteKeyRequest so the callback can cache the found key and return the skip decision. + if(check_passed && + mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { + bool auth_ok = false; + + while(!auth_ok) { + // Request next key from callback (tries dict entries) + memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData)); + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + instance->callback(instance->general_event, instance->context); + + if(!instance->mfu_event.data->key_request_data.key_provided) { + FURI_LOG_D(TAG, "ULC write: all keys exhausted"); + break; + } + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + + // Halt+activate so the card is in a clean state for auth + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(iso14443_3a_poller_activate(instance->iso14443_3a_poller, NULL) != + Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "ULC write: card not responding (locked out?)"); + break; + } + + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + if(mf_ultralight_poller_authenticate_start(instance, RndA, output) != + MfUltralightErrorNone) { + break; + } + uint8_t decoded_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + if(mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_RndA) != + MfUltralightErrorNone) + continue; + mf_ultralight_3des_shift_data(RndA); + auth_ok = (memcmp(RndA, decoded_RndA, sizeof(decoded_RndA)) == 0); + FURI_LOG_D(TAG, "ULC write auth attempt: %s", auth_ok ? "success" : "fail"); + } + + if(auth_ok) { + // Notify callback with the found key: it caches it and returns the skip decision + MfUltralightC3DesAuthKey found_key = instance->auth_context.tdes_key; + memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData)); + instance->mfu_event.data->key_request_data.key = found_key; + instance->mfu_event.data->key_request_data.key_provided = true; + instance->mfu_event.type = MfUltralightPollerEventTypeWriteKeyRequest; + instance->callback(instance->general_event, instance->context); + instance->write_skip_key = instance->mfu_event.data->write_key_skip; + FURI_LOG_D( + TAG, + "ULC write: key %s", + instance->write_skip_key ? "kept (target unchanged)" : "overwrite with source"); + } else { + FURI_LOG_E(TAG, "ULC write auth failed - card locked"); + check_passed = false; + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + } + } + if(!check_passed) { iso14443_3a_poller_halt(instance->iso14443_3a_poller); command = instance->callback(instance->general_event, instance->context); @@ -752,15 +820,52 @@ static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* i NfcCommand command = NfcCommandContinue; do { - const MfUltralightData* write_data = instance->mfu_event.data->write_data; + // Use the saved write_data pointer - the union was overwritten by WriteKeyRequest + const MfUltralightData* write_data = instance->write_data; uint8_t end_page = mf_ultralight_get_write_end_page(write_data->type); + + // If user chose to keep target's key, stop before the ULC key pages (44-47) + if(instance->write_skip_key && write_data->type == MfUltralightTypeMfulC && + end_page > 44) { + end_page = 44; + } + if(instance->current_page == end_page) { instance->state = MfUltralightPollerStateWriteSuccess; break; } - FURI_LOG_D(TAG, "Writing page %d", instance->current_page); - MfUltralightError error = mf_ultralight_poller_write_page( - instance, instance->current_page, &write_data->page[instance->current_page]); + + // For ULC key pages (44-47): byte-order correction required. + // Flipper stores each 8-byte DES sub-key MSB-first; the card expects each half reversed. + // Transform: card_bytes = reverse(flipper[0..7]) || reverse(flipper[8..15]) + MfUltralightPage page_to_write; + if(instance->current_page >= 44 && instance->current_page <= 47 && + write_data->type == MfUltralightTypeMfulC) { + const uint8_t* raw = (const uint8_t*)&write_data->page[44]; + uint8_t xformed[16]; + for(int i = 0; i < 8; i++) { + xformed[i] = raw[7 - i]; + } + for(int i = 0; i < 8; i++) { + xformed[8 + i] = raw[15 - i]; + } + uint8_t page_offset = (instance->current_page - 44) * 4; + memcpy(page_to_write.data, xformed + page_offset, 4); + FURI_LOG_D( + TAG, + "Writing KEY page %d (byte-order corrected): %02X %02X %02X %02X", + instance->current_page, + page_to_write.data[0], + page_to_write.data[1], + page_to_write.data[2], + page_to_write.data[3]); + } else { + page_to_write = write_data->page[instance->current_page]; + FURI_LOG_D(TAG, "Writing page %d", instance->current_page); + } + + MfUltralightError error = + mf_ultralight_poller_write_page(instance, instance->current_page, &page_to_write); if(error != MfUltralightErrorNone) { instance->state = MfUltralightPollerStateWriteFail; instance->error = error; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 2552abeb5..c0a38cfe9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -28,6 +28,7 @@ typedef enum { MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ + MfUltralightPollerEventTypeWriteKeyRequest, /**< Poller asks user whether to overwrite 3DES key on target. */ } MfUltralightPollerEventType; /** @@ -67,6 +68,7 @@ typedef union { const MfUltralightData* write_data; /**< Data to be written to card. */ MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ + bool write_key_skip; /**< Set to true by callback to skip writing 3DES key pages. */ } MfUltralightPollerEventData; /** diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index b35c49aea..a6d9235b0 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -88,6 +88,8 @@ struct MfUltralightPoller { uint8_t tearing_flag_read; uint8_t tearing_flag_total; uint16_t current_page; + bool write_skip_key; // If true, skip writing pages 44-47 (3DES key) during ULC write + const MfUltralightData* write_data; // Saved pointer to source data for write phase MfUltralightError error; mbedtls_des3_context des_context; diff --git a/lib/subghz/protocols/keyfinder.c b/lib/subghz/protocols/keyfinder.c new file mode 100644 index 000000000..beb8f0882 --- /dev/null +++ b/lib/subghz/protocols/keyfinder.c @@ -0,0 +1,363 @@ +#include "keyfinder.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolKeyFinder" + +static const SubGhzBlockConst subghz_protocol_keyfinder_const = { + .te_short = 400, + .te_long = 1200, + .te_delta = 150, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderKeyFinder { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint8_t end_count; +}; + +struct SubGhzProtocolEncoderKeyFinder { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + KeyFinderDecoderStepReset = 0, + KeyFinderDecoderStepSaveDuration, + KeyFinderDecoderStepCheckDuration, + KeyFinderDecoderStepFinish, +} KeyFinderDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_keyfinder_decoder = { + .alloc = subghz_protocol_decoder_keyfinder_alloc, + .free = subghz_protocol_decoder_keyfinder_free, + + .feed = subghz_protocol_decoder_keyfinder_feed, + .reset = subghz_protocol_decoder_keyfinder_reset, + + .get_hash_data = subghz_protocol_decoder_keyfinder_get_hash_data, + .serialize = subghz_protocol_decoder_keyfinder_serialize, + .deserialize = subghz_protocol_decoder_keyfinder_deserialize, + .get_string = subghz_protocol_decoder_keyfinder_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_keyfinder_encoder = { + .alloc = subghz_protocol_encoder_keyfinder_alloc, + .free = subghz_protocol_encoder_keyfinder_free, + + .deserialize = subghz_protocol_encoder_keyfinder_deserialize, + .stop = subghz_protocol_encoder_keyfinder_stop, + .yield = subghz_protocol_encoder_keyfinder_yield, +}; + +const SubGhzProtocol subghz_protocol_keyfinder = { + .name = SUBGHZ_PROTOCOL_KEYFINDER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_keyfinder_decoder, + .encoder = &subghz_protocol_keyfinder_encoder, +}; + +void* subghz_protocol_encoder_keyfinder_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderKeyFinder* instance = malloc(sizeof(SubGhzProtocolEncoderKeyFinder)); + + instance->base.protocol = &subghz_protocol_keyfinder; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 5; + instance->encoder.size_upload = 60; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_keyfinder_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKeyFinder* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +static void + subghz_protocol_encoder_keyfinder_get_upload(SubGhzProtocolEncoderKeyFinder* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key data 24 bit first + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_long); + + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short); + } + } + // End bits (3 times then 1 more with gap 4k us) + for(uint8_t i = 0; i < 3; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short * 10); + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_keyfinder_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 4; + instance->btn = instance->data & 0xF; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKeyFinder* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_keyfinder_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_keyfinder_check_remote_controller(&instance->generic); + subghz_protocol_encoder_keyfinder_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_keyfinder_stop(void* context) { + SubGhzProtocolEncoderKeyFinder* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_keyfinder_yield(void* context) { + SubGhzProtocolEncoderKeyFinder* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_keyfinder_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderKeyFinder* instance = malloc(sizeof(SubGhzProtocolDecoderKeyFinder)); + instance->base.protocol = &subghz_protocol_keyfinder; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_keyfinder_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + free(instance); +} + +void subghz_protocol_decoder_keyfinder_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + instance->decoder.parser_step = KeyFinderDecoderStepReset; +} + +void subghz_protocol_decoder_keyfinder_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + + // KeyFinder Decoder + // 2026.03 - @xMasterX (MMX) + + // Key samples + // + // 433.92 MHz AM650 + // Serial ID Serial ID + // RED - C396F E = 11000011100101101111 1110 + // PURPLE - C396F B = 11000011100101101111 1011 + // GREEN - C396F D = 11000011100101101111 1101 + // BLUE - C396F C = 11000011100101101111 1100 + + switch(instance->decoder.parser_step) { + case KeyFinderDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short * 10) < + subghz_protocol_keyfinder_const.te_delta * 5)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + } + break; + case KeyFinderDecoderStepSaveDuration: + if(instance->decoder.decode_count_bit == + subghz_protocol_keyfinder_const.min_count_bit_for_found) { + if((level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + instance->end_count++; + if(instance->end_count == 4) { + instance->decoder.parser_step = KeyFinderDecoderStepFinish; + instance->end_count = 0; + } + } else if( + (!level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + break; + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->end_count = 0; + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + } + + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KeyFinderDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + case KeyFinderDecoderStepCheckDuration: + if(!level) { + // Bit 1 is short and long timing = 400us HIGH (te_last) and 1200us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_long) < + subghz_protocol_keyfinder_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + // Bit 0 is long and short timing = 1200us HIGH (te_last) and 400us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keyfinder_const.te_long) < + subghz_protocol_keyfinder_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + case KeyFinderDecoderStepFinish: + // If got 24 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_keyfinder_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->end_count = 0; + instance->decoder.parser_step = KeyFinderDecoderStepReset; + break; + } +} + +uint8_t subghz_protocol_decoder_keyfinder_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_keyfinder_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_keyfinder_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_keyfinder_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + + subghz_protocol_keyfinder_check_remote_controller(&instance->generic); + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + // for future use + // // push protocol data to global variable + // subghz_block_generic_global.btn_is_available = false; + // subghz_block_generic_global.current_btn = instance->generic.btn; + // subghz_block_generic_global.btn_length_bit = 4; + // // + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%06lX\r\n" + "Yek: 0x%06lX\r\n" + "Serial: 0x%05lX\r\n" + "ID: 0x%0X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + (uint32_t)(code_found_reverse & 0xFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/keyfinder.h b/lib/subghz/protocols/keyfinder.h new file mode 100644 index 000000000..d37f24df4 --- /dev/null +++ b/lib/subghz/protocols/keyfinder.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KEYFINDER_NAME "KeyFinder" + +typedef struct SubGhzProtocolDecoderKeyFinder SubGhzProtocolDecoderKeyFinder; +typedef struct SubGhzProtocolEncoderKeyFinder SubGhzProtocolEncoderKeyFinder; + +extern const SubGhzProtocolDecoder subghz_protocol_keyfinder_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_keyfinder_encoder; +extern const SubGhzProtocol subghz_protocol_keyfinder; + +/** + * Allocate SubGhzProtocolEncoderKeyFinder. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKeyFinder* pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void* subghz_protocol_encoder_keyfinder_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKeyFinder. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void subghz_protocol_encoder_keyfinder_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void subghz_protocol_encoder_keyfinder_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_keyfinder_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderKeyFinder. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKeyFinder* pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void* subghz_protocol_decoder_keyfinder_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void subghz_protocol_decoder_keyfinder_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void subghz_protocol_decoder_keyfinder_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_keyfinder_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_keyfinder_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_keyfinder_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param output Resulting text + */ +void subghz_protocol_decoder_keyfinder_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index b87349cec..cf5fc5a9f 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -85,6 +85,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_beninca_arc, &subghz_protocol_jarolift, &subghz_protocol_ditec_gol4, + &subghz_protocol_keyfinder, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 0c5bb16a9..e712e8b0a 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -86,3 +86,4 @@ #include "beninca_arc.h" #include "jarolift.h" #include "ditec_gol4.h" +#include "keyfinder.h"