mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-10-22 17:51:15 +00:00
PSA faster BF and GUI
This commit is contained in:
1 parent
36c8dce72d
commit
a54b0ab4a0
9 files changed
+634
-354
No files matched your search
@@ -26,6 +26,8 @@ typedef enum {
|
||||
// File management
|
||||
ProtoPirateCustomEventReceiverInfoSave,
|
||||
ProtoPirateCustomEventReceiverInfoEmulate,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceStart,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
|
||||
ProtoPirateCustomEventSavedInfoDelete,
|
||||
// Emulator
|
||||
ProtoPirateCustomEventSavedInfoEmulate,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 448 B |
Binary file not shown.
|
After Width: | Height: | Size: 385 B |
+278
-246
@@ -22,10 +22,33 @@ static const SubGhzBlockConst subghz_protocol_psa_const = {
|
||||
#define PSA_MAX_BITS 0x79
|
||||
#define PSA_KEY1_BITS 0x40
|
||||
#define PSA_KEY2_BITS 0x50
|
||||
#define PSA_BUFFER_SIZE 48
|
||||
#define PSA_TE_LONG_300 0x12c
|
||||
|
||||
#define TEA_DELTA 0x9E3779B9U
|
||||
#define TEA_ROUNDS 32
|
||||
|
||||
typedef struct {
|
||||
uint32_t s0[TEA_ROUNDS];
|
||||
uint32_t s1[TEA_ROUNDS];
|
||||
} PsaTeaSchedule;
|
||||
|
||||
static void psa_tea_build_schedule(const uint32_t* key, PsaTeaSchedule* out) {
|
||||
for(int i = 0; i < TEA_ROUNDS; i++) {
|
||||
uint32_t sum0 = (uint32_t)((uint64_t)i * TEA_DELTA);
|
||||
uint32_t sum1 = (uint32_t)((uint64_t)(i + 1) * TEA_DELTA);
|
||||
out->s0[i] = key[sum0 & 3] + sum0;
|
||||
out->s1[i] = key[(sum1 >> 11) & 3] + sum1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void psa_tea_encrypt_with_schedule(uint32_t* v0, uint32_t* v1, const PsaTeaSchedule* sched) {
|
||||
for(int i = 0; i < TEA_ROUNDS; i++) {
|
||||
*v0 += (sched->s0[i] ^ (((*v1 >> 5) ^ (*v1 << 4)) + *v1));
|
||||
*v1 += (sched->s1[i] ^ (((*v0 >> 5) ^ (*v0 << 4)) + *v0));
|
||||
}
|
||||
}
|
||||
|
||||
#define PSA_BF1_CONST_U4 0x0E0F5C41U
|
||||
#define PSA_BF1_CONST_U5 0x0F5C4123U
|
||||
|
||||
@@ -154,6 +177,7 @@ static void psa_calculate_checksum(uint8_t* buffer);
|
||||
static uint8_t psa_calculate_tea_crc(uint32_t v0, uint32_t v1);
|
||||
static void psa_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key);
|
||||
static void psa_unpack_tea_result_to_buffer(uint8_t* buffer, uint32_t v0, uint32_t v1);
|
||||
static bool parse_hex_key_str(const char* str, uint64_t* out);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void psa_second_stage_xor_encrypt(uint8_t* buffer) {
|
||||
@@ -196,7 +220,7 @@ static void psa_build_buffer_mode23(
|
||||
(unsigned int)instance->crc,
|
||||
(unsigned int)instance->button);
|
||||
|
||||
memset(buffer, 0, 48);
|
||||
memset(buffer, 0, PSA_BUFFER_SIZE);
|
||||
|
||||
buffer[2] = (uint8_t)((instance->serial >> 16) & 0xFF);
|
||||
buffer[3] = (uint8_t)((instance->serial >> 8) & 0xFF);
|
||||
@@ -342,7 +366,7 @@ static void psa_build_buffer_mode36(
|
||||
(unsigned int)instance->crc,
|
||||
(unsigned int)instance->button);
|
||||
|
||||
memset(buffer, 0, 48);
|
||||
memset(buffer, 0, PSA_BUFFER_SIZE);
|
||||
|
||||
uint32_t v0 = ((instance->serial & 0xFFFFFF) << 8) | ((instance->counter >> 24) & 0xFF);
|
||||
uint32_t v1 = ((instance->counter & 0xFFFFFF) << 8) | ((instance->button & 0xF) << 4) |
|
||||
@@ -427,13 +451,13 @@ static void psa_encoder_build_upload(SubGhzProtocolEncoderPSA* instance) {
|
||||
(unsigned int)instance->crc,
|
||||
(unsigned int)instance->button);
|
||||
|
||||
uint8_t buffer[48] = {0};
|
||||
uint8_t buffer[PSA_BUFFER_SIZE] = {0};
|
||||
|
||||
uint8_t preserve_buffer01[2] = {0};
|
||||
uint8_t* preserve_ptr = NULL;
|
||||
|
||||
if(instance->key1_low != 0 || instance->key1_high != 0) {
|
||||
uint8_t orig_buffer[48] = {0};
|
||||
uint8_t orig_buffer[PSA_BUFFER_SIZE] = {0};
|
||||
psa_setup_byte_buffer(
|
||||
orig_buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
preserve_buffer01[0] = orig_buffer[0];
|
||||
@@ -792,6 +816,13 @@ static void psa_setup_byte_buffer(
|
||||
buffer[8] = (uint8_t)((key2_low >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
static bool psa_direct_xor_allowed_by_key2(uint8_t key2_high_byte) {
|
||||
uint8_t lo = key2_high_byte & 0xf;
|
||||
if(lo < 3) return true;
|
||||
if(lo < 7 && (key2_high_byte & 0xc) != 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void psa_calculate_checksum(uint8_t* buffer) {
|
||||
uint32_t checksum = 0;
|
||||
for(int i = 2; i < 8; i++) {
|
||||
@@ -835,7 +866,7 @@ static void psa_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) {
|
||||
}
|
||||
}
|
||||
|
||||
static void psa_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) {
|
||||
static inline void psa_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) {
|
||||
uint32_t sum = TEA_DELTA * TEA_ROUNDS;
|
||||
for(int i = 0; i < TEA_ROUNDS; i++) {
|
||||
uint32_t k_idx2 = (sum >> 11) & 3;
|
||||
@@ -914,14 +945,18 @@ static bool psa_brute_force_decrypt_bf1(
|
||||
uint8_t* buffer,
|
||||
uint32_t w0,
|
||||
uint32_t w1) {
|
||||
|
||||
PsaTeaSchedule bf1_sched;
|
||||
psa_tea_build_schedule(PSA_BF1_KEY_SCHEDULE, &bf1_sched);
|
||||
|
||||
for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) {
|
||||
uint32_t wk2 = PSA_BF1_CONST_U4;
|
||||
uint32_t wk3 = counter;
|
||||
psa_tea_encrypt(&wk2, &wk3, PSA_BF1_KEY_SCHEDULE);
|
||||
psa_tea_encrypt_with_schedule(&wk2, &wk3, &bf1_sched);
|
||||
|
||||
uint32_t wk0 = (counter << 8) | 0x0E;
|
||||
uint32_t wk1 = PSA_BF1_CONST_U5;
|
||||
psa_tea_encrypt(&wk0, &wk1, PSA_BF1_KEY_SCHEDULE);
|
||||
psa_tea_encrypt_with_schedule(&wk0, &wk1, &bf1_sched);
|
||||
|
||||
uint32_t working_key[4] = {wk0, wk1, wk2, wk3};
|
||||
|
||||
@@ -970,7 +1005,8 @@ static bool psa_brute_force_decrypt_bf2(
|
||||
(uint8_t)((dec_v1 >> 16) & 0xFF),
|
||||
};
|
||||
uint16_t crc16 = psa_calculate_crc16_bf2(crc_buffer, 6);
|
||||
uint16_t expected_crc = (((dec_v1 >> 16) & 0xFF) << 8) | (dec_v1 & 0xFF);
|
||||
|
||||
uint16_t expected_crc = (uint16_t)(dec_v1 & 0xFFFF);
|
||||
|
||||
if(crc16 == expected_crc) {
|
||||
psa_extract_fields_mode36(buffer, instance);
|
||||
@@ -981,6 +1017,163 @@ static bool psa_brute_force_decrypt_bf2(
|
||||
return false;
|
||||
}
|
||||
|
||||
static void psa_fill_bf_state_from_buffer(PsaBfState* state, uint8_t* buffer) {
|
||||
state->decrypted_button = (buffer[5] >> 4) & 0xF;
|
||||
state->decrypted_serial = ((uint32_t)buffer[3] << 8) | ((uint32_t)buffer[2] << 16) |
|
||||
(uint32_t)buffer[4];
|
||||
state->decrypted_counter = ((uint32_t)buffer[7] << 8) | ((uint32_t)buffer[6] << 16) |
|
||||
(uint32_t)buffer[8] | (((uint32_t)buffer[5] & 0xF) << 24);
|
||||
state->decrypted_crc = (uint16_t)buffer[9];
|
||||
state->decrypted_seed = state->decrypted_serial;
|
||||
state->decrypted_type = 0x36;
|
||||
}
|
||||
|
||||
#define PSA_BF_PROGRESS_INTERVAL 65536U
|
||||
|
||||
void psa_brute_force_run(PsaBfState* state) {
|
||||
uint8_t buffer[48] = {0};
|
||||
psa_setup_byte_buffer(buffer, state->key1_low, state->key1_high, state->key2_low);
|
||||
uint32_t w0, w1;
|
||||
psa_prepare_tea_data(buffer, &w0, &w1);
|
||||
|
||||
state->progress_current = 0;
|
||||
state->progress_total = (PSA_BF1_END - PSA_BF1_START) + (PSA_BF2_END - PSA_BF2_START);
|
||||
state->status = PSA_BF_STATUS_RUNNING;
|
||||
|
||||
PsaTeaSchedule bf1_sched;
|
||||
psa_tea_build_schedule(PSA_BF1_KEY_SCHEDULE, &bf1_sched);
|
||||
|
||||
for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) {
|
||||
if(state->cancel) {
|
||||
state->status = PSA_BF_STATUS_CANCELLED;
|
||||
return;
|
||||
}
|
||||
if((counter & (PSA_BF_PROGRESS_INTERVAL - 1)) == 0) {
|
||||
state->progress_current = counter - PSA_BF1_START;
|
||||
}
|
||||
|
||||
uint32_t wk2 = PSA_BF1_CONST_U4;
|
||||
uint32_t wk3 = counter;
|
||||
psa_tea_encrypt_with_schedule(&wk2, &wk3, &bf1_sched);
|
||||
uint32_t wk0 = (counter << 8) | 0x0E;
|
||||
uint32_t wk1 = PSA_BF1_CONST_U5;
|
||||
psa_tea_encrypt_with_schedule(&wk0, &wk1, &bf1_sched);
|
||||
uint32_t working_key[4] = {wk0, wk1, wk2, wk3};
|
||||
|
||||
uint32_t dec_v0 = w0;
|
||||
uint32_t dec_v1 = w1;
|
||||
psa_tea_decrypt(&dec_v0, &dec_v1, working_key);
|
||||
|
||||
if((counter & 0xFFFFFF) == (dec_v0 >> 8)) {
|
||||
uint8_t crc = psa_calculate_tea_crc(dec_v0, dec_v1);
|
||||
if(crc == (dec_v1 & 0xFF)) {
|
||||
psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1);
|
||||
psa_fill_bf_state_from_buffer(state, buffer);
|
||||
state->progress_current = counter - PSA_BF1_START;
|
||||
state->status = PSA_BF_STATUS_FOUND;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state->progress_current = PSA_BF1_END - PSA_BF1_START;
|
||||
|
||||
for(uint32_t counter = PSA_BF2_START; counter < PSA_BF2_END; counter++) {
|
||||
if(state->cancel) {
|
||||
state->status = PSA_BF_STATUS_CANCELLED;
|
||||
return;
|
||||
}
|
||||
if((counter & (PSA_BF_PROGRESS_INTERVAL - 1)) == 0) {
|
||||
state->progress_current =
|
||||
(PSA_BF1_END - PSA_BF1_START) + (counter - PSA_BF2_START);
|
||||
}
|
||||
|
||||
uint32_t working_key[4] = {
|
||||
PSA_BF2_KEY_SCHEDULE[0] ^ counter,
|
||||
PSA_BF2_KEY_SCHEDULE[1] ^ counter,
|
||||
PSA_BF2_KEY_SCHEDULE[2] ^ counter,
|
||||
PSA_BF2_KEY_SCHEDULE[3] ^ counter,
|
||||
};
|
||||
uint32_t dec_v0 = w0;
|
||||
uint32_t dec_v1 = w1;
|
||||
psa_tea_decrypt(&dec_v0, &dec_v1, working_key);
|
||||
|
||||
if((counter & 0xFFFFFF) == (dec_v0 >> 8)) {
|
||||
psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1);
|
||||
uint8_t crc_buffer[6] = {
|
||||
(uint8_t)((dec_v0 >> 24) & 0xFF),
|
||||
(uint8_t)((dec_v0 >> 8) & 0xFF),
|
||||
(uint8_t)((dec_v0 >> 16) & 0xFF),
|
||||
(uint8_t)(dec_v0 & 0xFF),
|
||||
(uint8_t)((dec_v1 >> 24) & 0xFF),
|
||||
(uint8_t)((dec_v1 >> 16) & 0xFF),
|
||||
};
|
||||
uint16_t crc16 = psa_calculate_crc16_bf2(crc_buffer, 6);
|
||||
uint16_t expected_crc = (uint16_t)(dec_v1 & 0xFFFF);
|
||||
if(crc16 == expected_crc) {
|
||||
psa_fill_bf_state_from_buffer(state, buffer);
|
||||
state->progress_current =
|
||||
(PSA_BF1_END - PSA_BF1_START) + (counter - PSA_BF2_START);
|
||||
state->status = PSA_BF_STATUS_FOUND;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state->status = PSA_BF_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
int32_t psa_brute_force_thread_entry(void* arg) {
|
||||
PsaBfState* state = arg;
|
||||
psa_brute_force_run(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool parse_hex_key_str(const char* str, uint64_t* out) {
|
||||
*out = 0;
|
||||
size_t len = str ? strlen(str) : 0;
|
||||
if(len > 16) len = 16;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if(c == ' ') continue;
|
||||
uint8_t nibble;
|
||||
if(c >= '0' && c <= '9') nibble = c - '0';
|
||||
else if(c >= 'A' && c <= 'F') nibble = c - 'A' + 10;
|
||||
else if(c >= 'a' && c <= 'f') nibble = c - 'a' + 10;
|
||||
else return false;
|
||||
*out = (*out << 4) | nibble;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool psa_bf_state_from_flipper_format(PsaBfState* state, FlipperFormat* ff) {
|
||||
furi_check(state);
|
||||
furi_check(ff);
|
||||
FuriString* temp = furi_string_alloc();
|
||||
bool ok = false;
|
||||
do {
|
||||
flipper_format_rewind(ff);
|
||||
if(!flipper_format_read_string(ff, "Key", temp)) break;
|
||||
uint64_t key1 = 0;
|
||||
if(!parse_hex_key_str(furi_string_get_cstr(temp), &key1)) break;
|
||||
state->key1_low = (uint32_t)(key1 & 0xFFFFFFFF);
|
||||
state->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF);
|
||||
|
||||
if(!flipper_format_read_string(ff, "Key_2", temp)) break;
|
||||
uint64_t key2 = 0;
|
||||
if(!parse_hex_key_str(furi_string_get_cstr(temp), &key2)) break;
|
||||
state->key2_low = (uint16_t)(key2 & 0xFFFF);
|
||||
|
||||
state->cancel = 0;
|
||||
state->progress_current = 0;
|
||||
state->progress_total = 0;
|
||||
state->status = PSA_BF_STATUS_IDLE;
|
||||
ok = true;
|
||||
} while(false);
|
||||
furi_string_free(temp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool psa_direct_xor_decrypt(SubGhzProtocolDecoderPSA* instance, uint8_t* buffer) {
|
||||
psa_calculate_checksum(buffer);
|
||||
uint8_t checksum = buffer[11];
|
||||
@@ -1008,6 +1201,56 @@ static bool psa_direct_xor_decrypt(SubGhzProtocolDecoderPSA* instance, uint8_t*
|
||||
return false;
|
||||
}
|
||||
|
||||
static void psa_handle_decoded_frame(SubGhzProtocolDecoderPSA* instance, uint8_t direct_xor_mode) {
|
||||
instance->key2_low = instance->decode_data_low;
|
||||
instance->key2_high = instance->decode_data_high;
|
||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||
instance->decrypted_type = 0;
|
||||
instance->decrypted_button = 0;
|
||||
instance->decrypted_serial = 0;
|
||||
instance->decrypted_counter = 0;
|
||||
instance->decrypted_crc = 0;
|
||||
instance->decrypted_seed = 0;
|
||||
instance->decrypted = 0x00;
|
||||
instance->mode_serialize =
|
||||
psa_direct_xor_allowed_by_key2((uint8_t)(instance->key2_low >> 8)) ? direct_xor_mode :
|
||||
0x36;
|
||||
instance->status_flag = 0x80;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Signal decoded - Key1:%08lX%08lX Key2:%04X ValField:%04X",
|
||||
(unsigned long)instance->key1_high,
|
||||
(unsigned long)instance->key1_low,
|
||||
(unsigned int)(instance->key2_low & 0xFFFF),
|
||||
(unsigned int)instance->validation_field);
|
||||
|
||||
uint8_t buffer[PSA_BUFFER_SIZE] = {0};
|
||||
psa_setup_byte_buffer(buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
if(instance->mode_serialize != 0x36 && psa_direct_xor_decrypt(instance, buffer)) {
|
||||
instance->mode_serialize = 0x23;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
} else {
|
||||
instance->decrypted = 0x00;
|
||||
instance->mode_serialize = 0x36;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string");
|
||||
}
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
}
|
||||
|
||||
static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) {
|
||||
FURI_LOG_I(TAG, "=== DECRYPTION ROUTER CALLED ===");
|
||||
FURI_LOG_I(
|
||||
@@ -1019,14 +1262,21 @@ static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) {
|
||||
(unsigned int)instance->mode_serialize,
|
||||
(unsigned int)instance->decrypted);
|
||||
|
||||
uint8_t buffer[48] = {0};
|
||||
uint8_t buffer[PSA_BUFFER_SIZE] = {0};
|
||||
|
||||
psa_setup_byte_buffer(buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
|
||||
uint8_t key2_high_byte = buffer[8];
|
||||
uint8_t mode = instance->mode_serialize;
|
||||
|
||||
if(mode == 1 || mode == 2) {
|
||||
if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
if(!psa_direct_xor_allowed_by_key2(key2_high_byte)) {
|
||||
mode = 0x36;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Key2 high byte 0x%02X fails OG direct-XOR filter -> 0x36 (TEA BF)",
|
||||
key2_high_byte);
|
||||
} else if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
mode = 0x23;
|
||||
FURI_LOG_I(TAG, "Converted mode %d -> 0x23 (Direct XOR)", instance->mode_serialize);
|
||||
} else {
|
||||
@@ -1046,48 +1296,19 @@ static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) {
|
||||
}
|
||||
FURI_LOG_W(TAG, "Direct XOR decryption FAILED");
|
||||
} else if(mode == 0x36) {
|
||||
FURI_LOG_I(TAG, "Attempting TEA brute force decryption (Type 0x36)");
|
||||
|
||||
uint32_t w0, w1;
|
||||
psa_prepare_tea_data(buffer, &w0, &w1);
|
||||
FURI_LOG_D(
|
||||
TAG, "Prepared TEA data: w0=0x%08lX w1=0x%08lX", (unsigned long)w0, (unsigned long)w1);
|
||||
|
||||
FURI_LOG_I(TAG, "Starting BF1 brute force...");
|
||||
if(psa_brute_force_decrypt_bf1(instance, buffer, w0, w1)) {
|
||||
instance->mode_serialize = 0x36;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"BF1 brute force SUCCESS - Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
return;
|
||||
}
|
||||
FURI_LOG_W(TAG, "BF1 brute force FAILED - trying BF2...");
|
||||
if(psa_brute_force_decrypt_bf2(instance, buffer, w0, w1)) {
|
||||
instance->mode_serialize = 0x36;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"BF2 brute force SUCCESS - Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
return;
|
||||
}
|
||||
FURI_LOG_E(TAG, "Both BF1 and BF2 brute force FAILED");
|
||||
FURI_LOG_I(TAG, "TEA BF deferred - use Brute force in receiver info");
|
||||
return;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Mode uninitialized (0x00) - trying direct XOR first");
|
||||
if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
FURI_LOG_I(TAG, "Mode uninitialized (0x00) - trying direct XOR or BF");
|
||||
if(psa_direct_xor_allowed_by_key2(key2_high_byte) && psa_direct_xor_decrypt(instance, buffer)) {
|
||||
instance->mode_serialize = 0x23;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(TAG, "Direct XOR decryption SUCCESS");
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Direct XOR failed - trying brute force...");
|
||||
FURI_LOG_I(TAG, "Direct XOR skipped or failed - trying brute force...");
|
||||
uint32_t w0, w1;
|
||||
psa_prepare_tea_data(buffer, &w0, &w1);
|
||||
|
||||
@@ -1290,58 +1511,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
instance->state = new_state;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t new_key2_low = instance->decode_data_low;
|
||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||
|
||||
instance->decrypted_type = 0;
|
||||
instance->decrypted_button = 0;
|
||||
instance->decrypted_serial = 0;
|
||||
instance->decrypted_counter = 0;
|
||||
instance->decrypted_crc = 0;
|
||||
instance->decrypted_seed = 0;
|
||||
instance->decrypted = 0x00;
|
||||
|
||||
instance->key2_low = new_key2_low;
|
||||
instance->key2_high = instance->decode_data_high;
|
||||
instance->mode_serialize = 1;
|
||||
instance->status_flag = 0x80;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Signal decoded - Key1:%08lX%08lX Key2:%04X ValField:%04X",
|
||||
(unsigned long)instance->key1_high,
|
||||
(unsigned long)instance->key1_low,
|
||||
(unsigned int)(instance->key2_low & 0xFFFF),
|
||||
(unsigned int)instance->validation_field);
|
||||
|
||||
uint8_t buffer[48] = {0};
|
||||
psa_setup_byte_buffer(
|
||||
buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
instance->mode_serialize = 0x23;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
} else {
|
||||
instance->decrypted = 0x00;
|
||||
instance->mode_serialize = 0x36;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string");
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
psa_handle_decoded_frame(instance, 1);
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
@@ -1458,57 +1628,10 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
}
|
||||
|
||||
if(((instance->key1_high >> 16) & 0xF) == 0xA) {
|
||||
uint32_t new_key2_low = instance->decode_data_low;
|
||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||
|
||||
instance->decrypted_type = 0;
|
||||
instance->decrypted_button = 0;
|
||||
instance->decrypted_serial = 0;
|
||||
instance->decrypted_counter = 0;
|
||||
instance->decrypted_crc = 0;
|
||||
instance->decrypted_seed = 0;
|
||||
instance->decrypted = 0x00;
|
||||
|
||||
instance->key2_low = new_key2_low;
|
||||
instance->key2_high = instance->decode_data_high;
|
||||
instance->status_flag = 0x80;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Signal decoded (alt path) - Key1:%08lX%08lX Key2:%04X ValField:%04X",
|
||||
(unsigned long)instance->key1_high,
|
||||
(unsigned long)instance->key1_low,
|
||||
(unsigned int)(instance->key2_low & 0xFFFF),
|
||||
(unsigned int)instance->validation_field);
|
||||
|
||||
uint8_t buffer[48] = {0};
|
||||
psa_setup_byte_buffer(
|
||||
buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
instance->mode_serialize = 0x23;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
} else {
|
||||
instance->decrypted = 0x00;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string");
|
||||
instance->mode_serialize = 0x36;
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
psa_handle_decoded_frame(instance, 1);
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -1520,8 +1643,8 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
|
||||
case PSADecoderState3:
|
||||
if(duration >= 250) {
|
||||
if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"[State3->State4] Transition detected with pattern_cnt=%lu",
|
||||
@@ -1587,7 +1710,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
tolerance = duration - PSA_TE_SHORT_125;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
manchester_input = ((level ^ 1) & 0x7f) << 1;
|
||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
} else if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) {
|
||||
if(level == 0) {
|
||||
manchester_input = 6;
|
||||
} else {
|
||||
@@ -1636,58 +1759,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
instance->state = new_state;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t new_key2_low = instance->decode_data_low;
|
||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||
|
||||
instance->decrypted_type = 0;
|
||||
instance->decrypted_button = 0;
|
||||
instance->decrypted_serial = 0;
|
||||
instance->decrypted_counter = 0;
|
||||
instance->decrypted_crc = 0;
|
||||
instance->decrypted_seed = 0;
|
||||
instance->decrypted = 0x00;
|
||||
|
||||
instance->key2_low = new_key2_low;
|
||||
instance->key2_high = instance->decode_data_high;
|
||||
instance->mode_serialize = 2;
|
||||
instance->status_flag = 0x80;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Signal decoded (state4) - Key1:%08lX%08lX Key2:%04X ValField:%04X",
|
||||
(unsigned long)instance->key1_high,
|
||||
(unsigned long)instance->key1_low,
|
||||
(unsigned int)(instance->key2_low & 0xFFFF),
|
||||
(unsigned int)instance->validation_field);
|
||||
|
||||
uint8_t buffer[48] = {0};
|
||||
psa_setup_byte_buffer(
|
||||
buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
if(psa_direct_xor_decrypt(instance, buffer)) {
|
||||
instance->mode_serialize = 0x23;
|
||||
instance->decrypted = 0x50;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX",
|
||||
(unsigned int)instance->decrypted_button,
|
||||
(unsigned long)instance->decrypted_serial,
|
||||
(unsigned long)instance->decrypted_counter);
|
||||
} else {
|
||||
instance->decrypted = 0x00;
|
||||
instance->mode_serialize = 0x36;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string");
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
psa_handle_decoded_frame(instance, 2);
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
@@ -1777,55 +1849,15 @@ SubGhzProtocolStatus
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* key1_str = furi_string_get_cstr(temp_str);
|
||||
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) break;
|
||||
uint64_t key1 = 0;
|
||||
size_t str_len = strlen(key1_str);
|
||||
for(size_t i = 0; i < str_len && i < 16; i++) {
|
||||
char c = key1_str[i];
|
||||
if(c == ' ') continue;
|
||||
|
||||
uint8_t nibble;
|
||||
if(c >= '0' && c <= '9') {
|
||||
nibble = c - '0';
|
||||
} else if(c >= 'A' && c <= 'F') {
|
||||
nibble = c - 'A' + 10;
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
nibble = c - 'a' + 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
key1 = (key1 << 4) | nibble;
|
||||
}
|
||||
if(!parse_hex_key_str(furi_string_get_cstr(temp_str), &key1)) break;
|
||||
instance->key1_low = (uint32_t)(key1 & 0xFFFFFFFF);
|
||||
instance->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF);
|
||||
|
||||
if(!flipper_format_read_string(flipper_format, "Key_2", temp_str)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* key2_str = furi_string_get_cstr(temp_str);
|
||||
if(!flipper_format_read_string(flipper_format, "Key_2", temp_str)) break;
|
||||
uint64_t key2 = 0;
|
||||
str_len = strlen(key2_str);
|
||||
for(size_t i = 0; i < str_len && i < 16; i++) {
|
||||
char c = key2_str[i];
|
||||
if(c == ' ') continue;
|
||||
|
||||
uint8_t nibble;
|
||||
if(c >= '0' && c <= '9') {
|
||||
nibble = c - '0';
|
||||
} else if(c >= 'A' && c <= 'F') {
|
||||
nibble = c - 'A' + 10;
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
nibble = c - 'a' + 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
key2 = (key2 << 4) | nibble;
|
||||
}
|
||||
if(!parse_hex_key_str(furi_string_get_cstr(temp_str), &key2)) break;
|
||||
instance->key2_low = (uint32_t)(key2 & 0xFFFFFFFF);
|
||||
instance->key2_high = (uint32_t)((key2 >> 32) & 0xFFFFFFFF);
|
||||
|
||||
@@ -1890,7 +1922,7 @@ void subghz_protocol_decoder_psa_get_string(void* context, FuriString* output) {
|
||||
"Key2:%04X\r\n"
|
||||
"Btn:%02X\r\n"
|
||||
"Ser:%06lX\r\n"
|
||||
"Cnt:%08lX\r\n"
|
||||
"Cnt:%lX\r\n"
|
||||
"CRC:%02X\r\n"
|
||||
"Type:%02X\r\n"
|
||||
"Sd:%06lX",
|
||||
|
||||
@@ -14,6 +14,37 @@
|
||||
|
||||
#define PSA_PROTOCOL_NAME "PSA"
|
||||
|
||||
#define PSA_BF_STATUS_IDLE 0
|
||||
#define PSA_BF_STATUS_RUNNING 1
|
||||
#define PSA_BF_STATUS_FOUND 2
|
||||
#define PSA_BF_STATUS_NOT_FOUND 3
|
||||
#define PSA_BF_STATUS_CANCELLED 4
|
||||
|
||||
typedef struct PsaBfState PsaBfState;
|
||||
struct PsaBfState {
|
||||
volatile uint8_t cancel;
|
||||
volatile uint32_t progress_current;
|
||||
volatile uint32_t progress_total;
|
||||
volatile uint8_t status;
|
||||
|
||||
uint32_t key1_low;
|
||||
uint32_t key1_high;
|
||||
uint16_t key2_low;
|
||||
|
||||
uint8_t decrypted_button;
|
||||
uint32_t decrypted_serial;
|
||||
uint32_t decrypted_counter;
|
||||
uint16_t decrypted_crc;
|
||||
uint32_t decrypted_seed;
|
||||
uint8_t decrypted_type;
|
||||
};
|
||||
|
||||
void psa_brute_force_run(PsaBfState* state);
|
||||
|
||||
int32_t psa_brute_force_thread_entry(void* arg);
|
||||
|
||||
bool psa_bf_state_from_flipper_format(PsaBfState* state, FlipperFormat* ff);
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPSA SubGhzProtocolDecoderPSA;
|
||||
typedef struct SubGhzProtocolEncoderPSA SubGhzProtocolEncoderPSA;
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ ProtoPirateApp* protopirate_app_alloc() {
|
||||
app->setting = subghz_setting_alloc();
|
||||
app->loaded_file_path = NULL;
|
||||
app->start_tx_time = 0;
|
||||
app->psa_bf_state = NULL;
|
||||
app->psa_bf_thread = NULL;
|
||||
subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user"));
|
||||
|
||||
LOG_HEAP("After subghz_setting");
|
||||
@@ -445,6 +447,17 @@ void protopirate_app_free(ProtoPirateApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
|
||||
protopirate_view_receiver_free(app->protopirate_receiver);
|
||||
|
||||
if(app->psa_bf_thread) {
|
||||
if(app->psa_bf_state) app->psa_bf_state->cancel = 1;
|
||||
furi_thread_join(app->psa_bf_thread);
|
||||
furi_thread_free(app->psa_bf_thread);
|
||||
app->psa_bf_thread = NULL;
|
||||
}
|
||||
if(app->psa_bf_state) {
|
||||
free(app->psa_bf_state);
|
||||
app->psa_bf_state = NULL;
|
||||
}
|
||||
|
||||
// Receiver Info
|
||||
FURI_LOG_D(TAG, "Removing receiver_info view");
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiverInfo);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include "defines.h"
|
||||
#include "protocols/psa.h"
|
||||
|
||||
#define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted")
|
||||
|
||||
@@ -66,6 +67,8 @@ struct ProtoPirateApp {
|
||||
ProtoPirateSettings settings;
|
||||
uint32_t start_tx_time;
|
||||
uint8_t tx_power;
|
||||
PsaBfState* psa_bf_state;
|
||||
FuriThread* psa_bf_thread;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -28,3 +28,8 @@ void protopirate_history_get_text_item(
|
||||
SubGhzProtocolDecoderBase*
|
||||
protopirate_history_get_decoder_base(ProtoPirateHistory* instance, uint16_t idx);
|
||||
FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, uint16_t idx);
|
||||
|
||||
void protopirate_history_set_item_str(
|
||||
ProtoPirateHistory* instance,
|
||||
uint16_t idx,
|
||||
const char* str);
|
||||
@@ -1,6 +1,7 @@
|
||||
// scenes/protopirate_scene_receiver_info.c
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "../helpers/protopirate_storage.h"
|
||||
#include "proto_pirate_icons.h"
|
||||
|
||||
#define TAG "ProtoPirateReceiverInfo"
|
||||
|
||||
@@ -9,36 +10,73 @@ static bool is_emu_off = false;
|
||||
static void protopirate_scene_receiver_info_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
ProtoPirateApp* app = context;
|
||||
if(type == InputTypeShort || type == InputTypeLong) {
|
||||
if(result == GuiButtonTypeRight) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave);
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
else if(result == GuiButtonTypeLeft && !is_emu_off) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoEmulate);
|
||||
}
|
||||
#endif
|
||||
void* context);
|
||||
|
||||
static bool psa_item_needs_bruteforce(ProtoPirateApp* app) {
|
||||
FlipperFormat* ff =
|
||||
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
if(!ff) return false;
|
||||
FuriString* s = furi_string_alloc();
|
||||
flipper_format_rewind(ff);
|
||||
bool has_key = flipper_format_read_string(ff, "Key", s);
|
||||
if(!has_key) {
|
||||
furi_string_free(s);
|
||||
return false;
|
||||
}
|
||||
flipper_format_rewind(ff);
|
||||
bool has_serial = flipper_format_read_string(ff, "Serial", s);
|
||||
furi_string_free(s);
|
||||
return !has_serial;
|
||||
}
|
||||
|
||||
#define PSA_BF_PROGRESS_BAR_X 62
|
||||
#define PSA_BF_PROGRESS_BAR_W 64
|
||||
#define PSA_BF_PROGRESS_BAR_Y 24
|
||||
#define PSA_BF_PROGRESS_BAR_H 8
|
||||
|
||||
static void protopirate_receiver_info_show_bf_progress(ProtoPirateApp* app) {
|
||||
widget_reset(app->widget);
|
||||
widget_add_icon_element(app->widget, 0, 5, &I_DolphinWait_59x54);
|
||||
widget_add_string_element(
|
||||
app->widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing...");
|
||||
PsaBfState* s = app->psa_bf_state;
|
||||
uint32_t cur = s->progress_current;
|
||||
uint32_t total = s->progress_total;
|
||||
uint8_t pct = total ? (uint8_t)((uint32_t)((uint64_t)cur * 100 / total)) : 0;
|
||||
if(pct > 100) pct = 100;
|
||||
|
||||
FuriString* pct_str = furi_string_alloc_printf("%u%%", pct);
|
||||
widget_add_string_element(
|
||||
app->widget, 62, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(pct_str));
|
||||
furi_string_free(pct_str);
|
||||
|
||||
widget_add_rect_element(
|
||||
app->widget,
|
||||
PSA_BF_PROGRESS_BAR_X,
|
||||
PSA_BF_PROGRESS_BAR_Y,
|
||||
PSA_BF_PROGRESS_BAR_W,
|
||||
PSA_BF_PROGRESS_BAR_H,
|
||||
2,
|
||||
false);
|
||||
if(pct > 0) {
|
||||
uint8_t fill_w = (PSA_BF_PROGRESS_BAR_W * (uint32_t)pct) / 100;
|
||||
if(fill_w < 2) fill_w = 2;
|
||||
widget_add_rect_element(
|
||||
app->widget,
|
||||
PSA_BF_PROGRESS_BAR_X + 1,
|
||||
PSA_BF_PROGRESS_BAR_Y + 1,
|
||||
fill_w - 1,
|
||||
PSA_BF_PROGRESS_BAR_H - 2,
|
||||
1,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateApp* app = context;
|
||||
|
||||
// Always reset per-enter to avoid stale static state
|
||||
is_emu_off = false;
|
||||
|
||||
static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) {
|
||||
widget_reset(app->widget);
|
||||
|
||||
FuriString* text;
|
||||
text = furi_string_alloc();
|
||||
|
||||
FuriString* text = furi_string_alloc();
|
||||
protopirate_history_get_text_item_menu(app->txrx->history, text, app->txrx->idx_menu_chosen);
|
||||
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(text));
|
||||
|
||||
@@ -52,81 +90,51 @@ void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
FuriString* protocol = furi_string_alloc();
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_string(ff, "Protocol", protocol)) {
|
||||
if(furi_string_cmp_str(protocol, "PSA") == 0) {
|
||||
is_psa = true;
|
||||
}
|
||||
if(furi_string_cmp_str(protocol, "Scher-Khan") == 0) {
|
||||
is_emu_off = true;
|
||||
} else if(furi_string_cmp_str(protocol, "Kia V5") == 0) {
|
||||
is_emu_off = true;
|
||||
} else if(furi_string_cmp_str(protocol, "Kia V6") == 0) {
|
||||
is_emu_off = true;
|
||||
} else {
|
||||
is_emu_off = false;
|
||||
}
|
||||
if(furi_string_cmp_str(protocol, "PSA") == 0) is_psa = true;
|
||||
if(furi_string_cmp_str(protocol, "Scher-Khan") == 0) is_emu_off = true;
|
||||
else if(furi_string_cmp_str(protocol, "Kia V5") == 0) is_emu_off = true;
|
||||
else if(furi_string_cmp_str(protocol, "Kia V6") == 0) is_emu_off = true;
|
||||
else is_emu_off = false;
|
||||
}
|
||||
furi_string_free(protocol);
|
||||
}
|
||||
|
||||
// Skip the first line (protocol name + Xbits) since it's already shown as the title
|
||||
const char* text_str = furi_string_get_cstr(text);
|
||||
const char* first_newline = strchr(text_str, '\r');
|
||||
if(first_newline) {
|
||||
// Skip \r\n
|
||||
text_str = first_newline + 1;
|
||||
if(*text_str == '\n') {
|
||||
text_str++;
|
||||
}
|
||||
if(*text_str == '\n') text_str++;
|
||||
} else {
|
||||
// Try \n only
|
||||
first_newline = strchr(text_str, '\n');
|
||||
if(first_newline) {
|
||||
text_str = first_newline + 1;
|
||||
}
|
||||
if(first_newline) text_str = first_newline + 1;
|
||||
}
|
||||
|
||||
if(is_psa) {
|
||||
FuriString* reformatted = furi_string_alloc();
|
||||
const char* current = text_str;
|
||||
|
||||
while(*current) {
|
||||
const char* line_end = strchr(current, '\r');
|
||||
if(!line_end) {
|
||||
line_end = strchr(current, '\n');
|
||||
}
|
||||
if(!line_end) {
|
||||
line_end = current + strlen(current);
|
||||
}
|
||||
if(!line_end) line_end = strchr(current, '\n');
|
||||
if(!line_end) line_end = current + strlen(current);
|
||||
|
||||
if(strncmp(current, "Ser:", 4) == 0) {
|
||||
size_t ser_len = line_end - current;
|
||||
furi_string_cat_printf(reformatted, "%.*s", (int)ser_len, current);
|
||||
|
||||
const char* next_line = line_end;
|
||||
if(*next_line == '\r') next_line++;
|
||||
if(*next_line == '\n') next_line++;
|
||||
|
||||
if(strncmp(next_line, "Cnt:", 4) == 0) {
|
||||
const char* cnt_end = strchr(next_line, '\r');
|
||||
if(!cnt_end) {
|
||||
cnt_end = strchr(next_line, '\n');
|
||||
}
|
||||
if(!cnt_end) {
|
||||
cnt_end = next_line + strlen(next_line);
|
||||
}
|
||||
|
||||
size_t cnt_len = cnt_end - next_line;
|
||||
furi_string_cat_printf(reformatted, " %.*s\r\n", (int)cnt_len, next_line);
|
||||
|
||||
if(!cnt_end) cnt_end = strchr(next_line, '\n');
|
||||
if(!cnt_end) cnt_end = next_line + strlen(next_line);
|
||||
furi_string_cat_printf(reformatted, " %.*s\r\n", (int)(cnt_end - next_line), next_line);
|
||||
current = cnt_end;
|
||||
if(*current == '\r') current++;
|
||||
if(*current == '\n') current++;
|
||||
} else {
|
||||
furi_string_cat_printf(reformatted, "\r\n");
|
||||
current = line_end;
|
||||
if(*current == '\r') current++;
|
||||
if(*current == '\n') current++;
|
||||
}
|
||||
if(*current == '\r') current++;
|
||||
if(*current == '\n') current++;
|
||||
} else {
|
||||
size_t line_len = line_end - current;
|
||||
furi_string_cat_printf(reformatted, "%.*s\r\n", (int)line_len, current);
|
||||
@@ -134,78 +142,274 @@ void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
if(*current == '\r') current++;
|
||||
if(*current == '\n') current++;
|
||||
}
|
||||
|
||||
if(*current == '\0') break;
|
||||
}
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
app->widget,
|
||||
0,
|
||||
11,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(reformatted));
|
||||
app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(reformatted));
|
||||
furi_string_free(reformatted);
|
||||
} else {
|
||||
widget_add_string_multiline_element(
|
||||
app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, text_str);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
// Add emulate button on the left
|
||||
if(!is_emu_off) {
|
||||
bool psa_needs_bf = is_psa && psa_item_needs_bruteforce(app);
|
||||
if(psa_needs_bf) {
|
||||
widget_add_button_element(
|
||||
app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Emulate",
|
||||
protopirate_scene_receiver_info_widget_callback,
|
||||
app);
|
||||
app->widget, GuiButtonTypeLeft, "Brute force",
|
||||
protopirate_scene_receiver_info_widget_callback, app);
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
else if(!is_emu_off) {
|
||||
widget_add_button_element(
|
||||
app->widget, GuiButtonTypeLeft, "Emulate",
|
||||
protopirate_scene_receiver_info_widget_callback, app);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add save button on the right
|
||||
widget_add_button_element(
|
||||
app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Save",
|
||||
protopirate_scene_receiver_info_widget_callback,
|
||||
app);
|
||||
app->widget, GuiButtonTypeRight, "Save",
|
||||
protopirate_scene_receiver_info_widget_callback, app);
|
||||
|
||||
furi_string_free(text);
|
||||
}
|
||||
|
||||
static void protopirate_receiver_info_show_bf_result(
|
||||
ProtoPirateApp* app,
|
||||
uint8_t status,
|
||||
PsaBfState* s) {
|
||||
UNUSED(s);
|
||||
widget_reset(app->widget);
|
||||
const char* title =
|
||||
(status == PSA_BF_STATUS_FOUND) ? "Found!" :
|
||||
(status == PSA_BF_STATUS_CANCELLED) ? "Cancelled" : "Not found";
|
||||
if(status == PSA_BF_STATUS_FOUND) {
|
||||
widget_add_icon_element(app->widget, 0, 3, &I_DolphinDone_80x58);
|
||||
widget_add_string_element(
|
||||
app->widget, 82, 32, AlignLeft, AlignCenter, FontPrimary, title);
|
||||
widget_add_button_element(
|
||||
app->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"OK",
|
||||
protopirate_scene_receiver_info_widget_callback,
|
||||
app);
|
||||
} else if(status == PSA_BF_STATUS_CANCELLED) {
|
||||
widget_add_string_element(app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title);
|
||||
widget_add_icon_element(app->widget, (128 - 45) / 2, 14, &I_WarningDolphin_45x42);
|
||||
} else {
|
||||
widget_add_string_element(app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title);
|
||||
}
|
||||
}
|
||||
|
||||
static void protopirate_scene_receiver_info_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
ProtoPirateApp* app = context;
|
||||
if(type == InputTypeShort || type == InputTypeLong) {
|
||||
if(result == GuiButtonTypeRight) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave);
|
||||
}
|
||||
else if(result == GuiButtonTypeLeft) {
|
||||
|
||||
if(!app->psa_bf_thread && psa_item_needs_bruteforce(app)) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoBruteforceStart);
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
else if(!is_emu_off) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoEmulate);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if(result == GuiButtonTypeCenter) {
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoBruteforceCancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateApp* app = context;
|
||||
|
||||
is_emu_off = false;
|
||||
|
||||
if(app->psa_bf_thread && app->psa_bf_state) {
|
||||
if(app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) {
|
||||
protopirate_receiver_info_show_bf_progress(app);
|
||||
} else {
|
||||
protopirate_receiver_info_show_bf_result(
|
||||
app, app->psa_bf_state->status, app->psa_bf_state);
|
||||
}
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
|
||||
return;
|
||||
}
|
||||
|
||||
protopirate_receiver_info_build_normal_widget(app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
|
||||
}
|
||||
|
||||
static void psa_bf_finish_and_show_result(ProtoPirateApp* app) {
|
||||
PsaBfState* s = app->psa_bf_state;
|
||||
uint8_t status = s->status;
|
||||
if(app->psa_bf_thread) {
|
||||
furi_thread_join(app->psa_bf_thread);
|
||||
furi_thread_free(app->psa_bf_thread);
|
||||
app->psa_bf_thread = NULL;
|
||||
}
|
||||
if(status == PSA_BF_STATUS_FOUND) {
|
||||
FlipperFormat* ff =
|
||||
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
if(ff) {
|
||||
flipper_format_rewind(ff);
|
||||
flipper_format_insert_or_update_uint32(ff, "Serial", &s->decrypted_serial, 1);
|
||||
uint32_t btn = s->decrypted_button;
|
||||
flipper_format_insert_or_update_uint32(ff, "Btn", &btn, 1);
|
||||
flipper_format_insert_or_update_uint32(ff, "Cnt", &s->decrypted_counter, 1);
|
||||
uint32_t type = s->decrypted_type;
|
||||
flipper_format_insert_or_update_uint32(ff, "Type", &type, 1);
|
||||
uint32_t crc_val = s->decrypted_crc;
|
||||
flipper_format_insert_or_update_uint32(ff, "CRC", &crc_val, 1);
|
||||
flipper_format_insert_or_update_uint32(ff, "Seed", &s->decrypted_seed, 1);
|
||||
}
|
||||
FuriString* new_str = furi_string_alloc_printf(
|
||||
"PSA 128bit\r\n"
|
||||
"Key1:%08lX%08lX\r\n"
|
||||
"Key2:%04X\r\n"
|
||||
"Btn:%02X\r\n"
|
||||
"Ser:%06lX\r\n"
|
||||
"Cnt:%lX\r\n"
|
||||
"Type:%02X\r\n"
|
||||
"Sd:%06lX",
|
||||
(unsigned long)s->key1_high, (unsigned long)s->key1_low,
|
||||
(unsigned int)(s->key2_low & 0xFFFF),
|
||||
(unsigned int)s->decrypted_button,
|
||||
(unsigned long)s->decrypted_serial,
|
||||
(unsigned long)s->decrypted_counter,
|
||||
(unsigned int)s->decrypted_type,
|
||||
(unsigned long)s->decrypted_seed);
|
||||
protopirate_history_set_item_str(
|
||||
app->txrx->history, app->txrx->idx_menu_chosen, furi_string_get_cstr(new_str));
|
||||
furi_string_free(new_str);
|
||||
}
|
||||
if(status == PSA_BF_STATUS_FOUND) {
|
||||
|
||||
protopirate_receiver_info_show_bf_result(app, status, s);
|
||||
|
||||
} else {
|
||||
free(app->psa_bf_state);
|
||||
app->psa_bf_state = NULL;
|
||||
protopirate_receiver_info_show_bf_result(app, status, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
|
||||
ProtoPirateApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_FOUND) {
|
||||
|
||||
protopirate_receiver_info_build_normal_widget(app);
|
||||
free(app->psa_bf_state);
|
||||
app->psa_bf_state = NULL;
|
||||
consumed = true;
|
||||
} else if(app->psa_bf_thread && app->psa_bf_state &&
|
||||
app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) {
|
||||
app->psa_bf_state->cancel = 1;
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
if(app->psa_bf_thread && app->psa_bf_state) {
|
||||
if(app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) {
|
||||
protopirate_receiver_info_show_bf_progress(app);
|
||||
consumed = true;
|
||||
} else {
|
||||
psa_bf_finish_and_show_result(app);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == ProtoPirateCustomEventReceiverInfoSave) {
|
||||
// Get the flipper format from history
|
||||
if(event.event == ProtoPirateCustomEventReceiverInfoBruteforceStart) {
|
||||
FlipperFormat* ff =
|
||||
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
if(!ff || !psa_item_needs_bruteforce(app)) {
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
PsaBfState* state = malloc(sizeof(PsaBfState));
|
||||
if(!state) {
|
||||
notification_message(app->notifications, &sequence_error);
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
if(!psa_bf_state_from_flipper_format(state, ff)) {
|
||||
free(state);
|
||||
notification_message(app->notifications, &sequence_error);
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
app->psa_bf_state = state;
|
||||
app->psa_bf_thread = furi_thread_alloc_ex(
|
||||
"PsaBf", 2048, psa_brute_force_thread_entry, state);
|
||||
if(!app->psa_bf_thread) {
|
||||
free(state);
|
||||
app->psa_bf_state = NULL;
|
||||
notification_message(app->notifications, &sequence_error);
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
furi_thread_start(app->psa_bf_thread);
|
||||
protopirate_receiver_info_show_bf_progress(app);
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if(event.event == ProtoPirateCustomEventReceiverInfoBruteforceCancel) {
|
||||
if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_FOUND) {
|
||||
|
||||
protopirate_receiver_info_build_normal_widget(app);
|
||||
free(app->psa_bf_state);
|
||||
app->psa_bf_state = NULL;
|
||||
} else if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) {
|
||||
app->psa_bf_state->cancel = 1;
|
||||
} else {
|
||||
if(app->psa_bf_state) {
|
||||
psa_bf_finish_and_show_result(app);
|
||||
}
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
consumed = true;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if(event.event == ProtoPirateCustomEventReceiverInfoSave) {
|
||||
FlipperFormat* ff =
|
||||
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
if(ff) {
|
||||
// Extract protocol name
|
||||
FuriString* protocol = furi_string_alloc();
|
||||
flipper_format_rewind(ff);
|
||||
if(!flipper_format_read_string(ff, "Protocol", protocol)) {
|
||||
furi_string_set_str(protocol, "Unknown");
|
||||
}
|
||||
|
||||
FuriString* saved_path = furi_string_alloc();
|
||||
if(protopirate_storage_save_capture(
|
||||
ff, furi_string_get_cstr(protocol), saved_path)) {
|
||||
// Show success notification
|
||||
notification_message(app->notifications, &sequence_success);
|
||||
FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path));
|
||||
} else {
|
||||
notification_message(app->notifications, &sequence_error);
|
||||
FURI_LOG_E(TAG, "Save failed");
|
||||
}
|
||||
|
||||
furi_string_free(protocol);
|
||||
furi_string_free(saved_path);
|
||||
}
|
||||
@@ -213,26 +417,16 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
else if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) {
|
||||
// Get the flipper format from history
|
||||
FlipperFormat* ff =
|
||||
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
|
||||
if(ff) {
|
||||
// Save to temp file (will be deleted on emulate exit)
|
||||
if(protopirate_storage_save_temp(ff)) {
|
||||
FURI_LOG_I(TAG, "Saved temp for emulate");
|
||||
|
||||
// Set the temp file path for emulate scene
|
||||
if(app->loaded_file_path) {
|
||||
furi_string_free(app->loaded_file_path);
|
||||
}
|
||||
if(app->loaded_file_path) furi_string_free(app->loaded_file_path);
|
||||
app->loaded_file_path = furi_string_alloc_set_str(PROTOPIRATE_TEMP_FILE);
|
||||
|
||||
// Go to emulate scene
|
||||
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate);
|
||||
} else {
|
||||
notification_message(app->notifications, &sequence_error);
|
||||
FURI_LOG_E(TAG, "Failed to save temp for emulate");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "No flipper format data for index %d", app->txrx->idx_menu_chosen);
|
||||
|
||||
Reference in new issue
Block a user