diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 3eb24096b..639a3fca7 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -319,6 +319,16 @@ App( sources=["nfc_cli.c"], ) +App( + appid="disney_infinity_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="disney_infinity_plugin_ep", + targets=["f7"], + requires=["nfc"], + fap_libs=["mbedtls"], + sources=["plugins/supported_cards/disney_infinity.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/disney_infinity.c b/applications/main/nfc/plugins/supported_cards/disney_infinity.c new file mode 100644 index 000000000..a98d39ec2 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/disney_infinity.c @@ -0,0 +1,121 @@ +#include +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include +#include + +#define TAG "DisneyInfinity" +#define UID_LEN 7 + +// Derived from https://nfc.toys/#new-interoperability-for-infinity +static uint8_t seed[38] = {0x0A, 0x14, 0xFD, 0x05, 0x07, 0xFF, 0x4B, 0xCD, 0x02, 0x6B, + 0xA8, 0x3F, 0x0A, 0x3B, 0x89, 0xA9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, + 0x6E, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; + +void di_key(const uint8_t* uid, MfClassicKey* key) { + uint8_t hash[20]; + memcpy(seed + 16, uid, UID_LEN); + mbedtls_sha1(seed, sizeof(seed), hash); + key->data[0] = hash[3]; + key->data[1] = hash[2]; + key->data[2] = hash[1]; + key->data[3] = hash[0]; + key->data[4] = hash[7]; + key->data[5] = hash[6]; +} + +static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + size_t* uid_len = 0; + bool is_read = false; + MfClassicData* data = mf_classic_alloc(); + + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + MfClassicDeviceKeys keys = {}; + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + di_key(uid_bytes, &keys.key_a[i]); + di_key(uid_bytes, &keys.key_b[i]); + FURI_BIT_SET(keys.key_a_mask, i); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data: %d", error); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool disney_infinity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + size_t* uid_len = 0; + bool parsed = false; + FuriString* name = furi_string_alloc(); + const uint8_t verify_sector = 0; + MfClassicKey key = {}; + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + + do { + // verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + + di_key(uid_bytes, &key); + if(memcmp(key.data, sec_tr->key_a.data, 6) != 0) break; + + // At some point I'd like to add name lookup like Skylanders + furi_string_printf(parsed_data, "\e#Disney Infinity\n"); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin disney_infinity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, // Need UID to verify key(s) + .read = disney_infinity_read, + .parse = disney_infinity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor disney_infinity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &disney_infinity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* disney_infinity_plugin_ep(void) { + return &disney_infinity_plugin_descriptor; +} diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index e23b9c4db..dd9d6c358 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -617,7 +617,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(furi_string_size(args)) { char* args_cstr = (char*)furi_string_get_cstr(args); StrintParseError parse_err = StrintParseNoError; - parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); if(parse_err) { cli_print_usage( diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 0e65dc045..5956407a9 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -20,6 +20,7 @@ #include "protocol_nexwatch.h" #include "protocol_securakey.h" #include "protocol_gproxii.h" +#include "protocol_noralsy.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -45,4 +46,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolNexwatch] = &protocol_nexwatch, [LFRFIDProtocolSecurakey] = &protocol_securakey, [LFRFIDProtocolGProxII] = &protocol_gproxii, + [LFRFIDProtocolNoralsy] = &protocol_noralsy, }; diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 9621bc408..e6194b827 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -32,6 +32,7 @@ typedef enum { LFRFIDProtocolNexwatch, LFRFIDProtocolSecurakey, LFRFIDProtocolGProxII, + LFRFIDProtocolNoralsy, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_noralsy.c b/lib/lfrfid/protocols/protocol_noralsy.c new file mode 100644 index 000000000..27cf8cb6b --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define NORALSY_CLOCK_PER_BIT (32) + +#define NORALSY_ENCODED_BIT_SIZE (96) +#define NORALSY_ENCODED_BYTE_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_PREAMBLE_BIT_SIZE (32) +#define NORALSY_PREAMBLE_BYTE_SIZE ((NORALSY_PREAMBLE_BIT_SIZE) / 8) +#define NORALSY_ENCODED_BYTE_FULL_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_DECODED_DATA_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) + +#define NORALSY_READ_SHORT_TIME (128) +#define NORALSY_READ_LONG_TIME (256) +#define NORALSY_READ_JITTER_TIME (60) + +#define NORALSY_READ_SHORT_TIME_LOW (NORALSY_READ_SHORT_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_SHORT_TIME_HIGH (NORALSY_READ_SHORT_TIME + NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_LOW (NORALSY_READ_LONG_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_HIGH (NORALSY_READ_LONG_TIME + NORALSY_READ_JITTER_TIME) + +#define TAG "NORALSY" + +typedef struct { + uint8_t data[NORALSY_ENCODED_BYTE_SIZE]; + uint8_t encoded_data[NORALSY_ENCODED_BYTE_SIZE]; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolNoralsy; + +ProtocolNoralsy* protocol_noralsy_alloc(void) { + ProtocolNoralsy* protocol = malloc(sizeof(ProtocolNoralsy)); + return (void*)protocol; +} + +void protocol_noralsy_free(ProtocolNoralsy* protocol) { + free(protocol); +} + +static uint8_t noralsy_chksum(uint8_t* bits, uint8_t len) { + uint8_t sum = 0; + for(uint8_t i = 0; i < len; i += 4) + sum ^= bit_lib_get_bits(bits, i, 4); + return sum & 0x0F; +} + +uint8_t* protocol_noralsy_get_data(ProtocolNoralsy* protocol) { + return protocol->data; +} + +static void protocol_noralsy_decode(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->encoded_data, 0); +} + +static bool protocol_noralsy_can_be_decoded(ProtocolNoralsy* protocol) { + // check 12 bits preamble + // If necessary, use 0xBB0214FF for 32 bit preamble check + // However, it is not confirmed the 13-16 bit are static. + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 12) != 0b101110110000) return false; + uint8_t calc1 = noralsy_chksum(&protocol->encoded_data[4], 40); + uint8_t calc2 = noralsy_chksum(&protocol->encoded_data[0], 76); + uint8_t chk1 = bit_lib_get_bits(protocol->encoded_data, 72, 4); + uint8_t chk2 = bit_lib_get_bits(protocol->encoded_data, 76, 4); + if(calc1 != chk1 || calc2 != chk2) return false; + + return true; +} + +void protocol_noralsy_decoder_start(ProtocolNoralsy* protocol) { + memset(protocol->encoded_data, 0, NORALSY_ENCODED_BYTE_FULL_SIZE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +} + +bool protocol_noralsy_decoder_feed(ProtocolNoralsy* protocol, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > NORALSY_READ_SHORT_TIME_LOW && duration < NORALSY_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > NORALSY_READ_LONG_TIME_LOW && duration < NORALSY_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, NORALSY_ENCODED_BYTE_FULL_SIZE, data); + + if(protocol_noralsy_can_be_decoded(protocol)) { + protocol_noralsy_decode(protocol); + result = true; + } + } + } + + return result; +} + +bool protocol_noralsy_encoder_start(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->encoded_data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->data, 0); + + return true; +} + +LevelDuration protocol_noralsy_encoder_yield(ProtocolNoralsy* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = NORALSY_CLOCK_PER_BIT / 2; + + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, NORALSY_ENCODED_BIT_SIZE); + } + + return level_duration_make(level, duration); +} + +bool protocol_noralsy_write_data(ProtocolNoralsy* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + // Correct protocol data by redecoding + protocol_noralsy_encoder_start(protocol); + protocol_noralsy_decode(protocol); + + protocol_noralsy_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT) | LFRFID_T5577_ST_TERMINATOR); + // In fact, base on the current two dump samples from Iceman server, + // Noralsy are usually T5577s with config = 0x00088C6A + // But the `C` and `A` are not explainable by the ATA5577C datasheet + // and they don't affect reading whatsoever. + // So we are mimicing Proxmark's solution here. Leave those nibbles as zero. + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +} + +static void protocol_noralsy_render_data_internal(ProtocolNoralsy* protocol, FuriString* result) { + UNUSED(protocol); + uint32_t raw2 = bit_lib_get_bits_32(protocol->data, 32, 32); + uint32_t raw3 = bit_lib_get_bits_32(protocol->data, 64, 32); + uint32_t cardid = ((raw2 & 0xFFF00000) >> 20) << 16; + cardid |= (raw2 & 0xFF) << 8; + cardid |= ((raw3 & 0xFF000000) >> 24); + + uint8_t year = (raw2 & 0x000ff000) >> 12; + bool tag_is_gen_z = (year > 0x60); + furi_string_printf( + result, + "Card ID: %07lx\n" + "Year: %s%02x", + cardid, + tag_is_gen_z ? "19" : "20", + year); +} + +void protocol_noralsy_render_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +void protocol_noralsy_render_brief_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +const ProtocolBase protocol_noralsy = { + .name = "Noralsy", + .manufacturer = "Noralsy", + .data_size = NORALSY_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_noralsy_alloc, + .free = (ProtocolFree)protocol_noralsy_free, + .get_data = (ProtocolGetData)protocol_noralsy_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_noralsy_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_noralsy_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_noralsy_encoder_start, + .yield = (ProtocolEncoderYield)protocol_noralsy_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_noralsy_render_data, + .render_brief_data = (ProtocolRenderData)protocol_noralsy_render_brief_data, + .write_data = (ProtocolWriteData)protocol_noralsy_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_noralsy.h b/lib/lfrfid/protocols/protocol_noralsy.h new file mode 100644 index 000000000..b1ee51a2e --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_noralsy;