mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-03-30 05:09:53 +00:00
Compare commits
7 Commits
dev-497420
...
dev-41d10f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d10f9b3d | ||
|
|
1f97aa2e3c | ||
|
|
5b9038173b | ||
|
|
fde0a57595 | ||
|
|
3fb40944e6 | ||
|
|
e61cfa765a | ||
|
|
fd0dd6c324 |
@@ -182,7 +182,7 @@ Contributions are welcome if they:
|
||||
> Non-automotive features are considered out-of-scope for now.
|
||||
|
||||
### This code is a mess!
|
||||

|
||||

|
||||
---
|
||||
|
||||
## Citations & References
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -92,19 +92,38 @@ Hopping_Preset: FM95
|
||||
# Custom preset
|
||||
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
|
||||
|
||||
Custom_preset_name: A1
|
||||
Custom_preset_name: OOK_LR
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 00 12 30 11 F8 10 C9 15 14 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 00 21 55 00 00 00 С0 00 00 00 00 00 00
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: OOK_U
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 17 0C 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 2C 81 2D 35 2E 09 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: AM_1
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 C9 11 F8 12 30 14 00 15 14 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 55 22 00 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: FSK_1
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 C7 11 93 12 00 13 22 14 F8 15 35 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 1D 34 60 84 C8 C0
|
||||
|
||||
Custom_preset_name: FSK_3
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0E 0F 0F 10 C5 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
|
||||
|
||||
Custom_preset_name: FM95
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: F3
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0E B0 0F 71 10 CA 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
|
||||
|
||||
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
|
||||
Custom_preset_name: FM15k
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
|
||||
Custom_preset_name: AU_1 (test auto FSK)
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 5B 11 F8 12 00 13 22 14 F8 15 47 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 34 60 84 C8 C0
|
||||
|
||||
Custom_preset_name: RF_1 (test sniff vario FSK/OOK hybrid)
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 7B 11 83 12 00 13 22 14 F8 15 50 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 34 60 84 C8 C0
|
||||
|
||||
@@ -133,21 +133,21 @@ 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;
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
|
||||
//CC1101 Stop Tx -> next tick event Start RX
|
||||
// user release OK
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -456,7 +456,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
|
||||
if(instance->mfu_event.data->key_request_data.key_provided) {
|
||||
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
|
||||
} else if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||
// TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
|
||||
// TODO: -nofl Can logic be rearranged to request this key
|
||||
// before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
|
||||
FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary");
|
||||
// Trigger dictionary key request
|
||||
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
|
||||
@@ -697,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 {
|
||||
@@ -737,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);
|
||||
@@ -751,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1055,10 +1055,10 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller_selector(
|
||||
case KEELOQ_LEARNING_SECURE:
|
||||
bool reset_seed_back = false;
|
||||
if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) {
|
||||
if(instance->seed == 0) {
|
||||
instance->seed = (fix & 0xFFFFFFF);
|
||||
reset_seed_back = true;
|
||||
}
|
||||
//if(instance->seed == 0) {
|
||||
instance->seed = (fix & 0xFFFFFFF);
|
||||
reset_seed_back = true;
|
||||
//}
|
||||
}
|
||||
man = subghz_protocol_keeloq_common_secure_learning(
|
||||
fix, instance->seed, manufacture_code->key);
|
||||
|
||||
Reference in New Issue
Block a user