Merge branch 'dev' into reborned/nfc_ui_refactor_2

This commit is contained in:
gornekich
2023-12-28 21:41:45 +04:00
committed by GitHub
6 changed files with 484 additions and 2 deletions

View File

@@ -65,6 +65,15 @@ App(
sources=["plugins/supported_cards/troika.c"],
)
App(
appid="washcity_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="washcity_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/washcity.c"],
)
App(
appid="plantain_parser",
apptype=FlipperAppType.PLUGIN,
@@ -101,6 +110,15 @@ App(
sources=["plugins/supported_cards/umarsh.c"],
)
App(
appid="hid_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="hid_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/hid.c"],
)
App(
appid="nfc_start",
targets=["f7"],

View File

@@ -0,0 +1,153 @@
#include "nfc_supported_card_plugin.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#define TAG "HID"
static const uint64_t hid_key = 0x484944204953;
bool hid_verify(Nfc* nfc) {
bool verified = false;
do {
const uint8_t verify_sector = 1;
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
MfClassicKey key = {};
nfc_util_num2bytes(hid_key, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_ctx = {};
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
if(error != MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}
verified = true;
} while(false);
return verified;
}
static bool hid_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);
bool is_read = false;
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do {
MfClassicType type = MfClassicType1k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;
data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_b[i].data);
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");
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = true;
} while(false);
mf_classic_free(data);
return is_read;
}
static uint8_t get_bit_length(const uint8_t* half_block) {
uint8_t bitLength = 0;
uint32_t* halves = (uint32_t*)half_block;
if(halves[0] == 0) {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
bitLength = 31 - leading0s;
} else {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
bitLength = 63 - leading0s;
}
return bitLength;
}
static uint64_t get_pacs_bits(const uint8_t* block, uint8_t bitLength) {
// Remove sentinel bit from credential. Byteswapping to handle array of bytes vs 64bit value
uint64_t sentinel = __builtin_bswap64(1ULL << bitLength);
uint64_t swapped = 0;
memcpy(&swapped, block, sizeof(uint64_t));
swapped = __builtin_bswap64(swapped ^ sentinel);
FURI_LOG_D(TAG, "PACS: (%d) %016llx", bitLength, swapped);
return swapped;
}
static bool hid_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// verify key
const uint8_t verify_sector = 1;
MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, verify_sector);
uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6);
if(key != hid_key) break;
// Currently doesn't support bit length > 63
const uint8_t* credential_block = data->block[5].data + 8;
uint8_t bitLength = get_bit_length(credential_block);
if(bitLength == 0) break;
uint64_t credential = get_pacs_bits(credential_block, bitLength);
if(credential == 0) break;
furi_string_printf(parsed_data, "\e#HID Card\n%dbit\n%llx", bitLength, credential);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin hid_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = hid_verify,
.read = hid_read,
.parse = hid_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor hid_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &hid_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* hid_plugin_ep() {
return &hid_plugin_descriptor;
}

View File

@@ -0,0 +1,200 @@
/*
* Parser for WashCity MarkItaly Card (Europe).
*
* Copyright 2023 Filipe Polido (YaBaPT) <polido@gmail.com>
*
* Based on MetroMoney by Leptoptilos <leptoptilos@icloud.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nfc_supported_card_plugin.h"
#include "protocols/mf_classic/mf_classic.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <stdint.h>
#define TAG "WashCity"
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
static const MfClassicKeyPair washcity_1k_keys[] = {
{.a = 0xA0A1A2A3A4A5, .b = 0x010155010100}, // Sector 00
{.a = 0xC78A3D0E1BCD, .b = 0xFFFFFFFFFFFF}, // Sector 01
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 02
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 03
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 04
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 05
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 06
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 07
{.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 08
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 09
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 10
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 11
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 12
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 13
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 14
{.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 15
};
static bool washcity_verify(Nfc* nfc) {
bool verified = false;
do {
const uint8_t ticket_sector_number = 0;
const uint8_t ticket_block_number =
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
MfClassicKey key = {0};
nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_context;
MfClassicError error = mf_classic_poller_sync_auth(
nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
if(error != MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
break;
}
verified = true;
} while(false);
return verified;
}
static bool washcity_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);
bool is_read = false;
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do {
MfClassicType type = MfClassicTypeMini;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;
data->type = type;
if(type != MfClassicType1k) break;
MfClassicDeviceKeys keys = {
.key_a_mask = 0,
.key_b_mask = 0,
};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
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");
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = true;
} while(false);
mf_classic_free(data);
return is_read;
}
static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// Verify key
const uint8_t ticket_sector_number = 1;
const uint8_t ticket_block_number = 0;
const MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
if(key != washcity_1k_keys[ticket_sector_number].a) break;
// Parse data
const uint8_t start_block_num =
mf_classic_get_first_block_num_of_sector(ticket_sector_number);
const uint8_t* block_start_ptr =
&data->block[start_block_num + ticket_block_number].data[0];
uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2);
uint32_t balance_eur = balance / 100;
uint8_t balance_cents = balance % 100;
size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
// Card Number is printed in HEX (equal to UID)
char card_number[2 * uid_len + 1];
for(size_t i = 0; i < uid_len; ++i) {
card_number[2 * i] = "0123456789ABCDEF"[uid[i] >> 4];
card_number[2 * i + 1] = "0123456789ABCDEF"[uid[i] & 0xF];
}
card_number[2 * uid_len] = '\0';
furi_string_printf(
parsed_data,
"\e#WashCity\nCard number: %s\nBalance: %lu.%02u EUR",
card_number,
balance_eur,
balance_cents);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin washcity_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = washcity_verify,
.read = washcity_read,
.parse = washcity_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor washcity_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &washcity_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* washcity_plugin_ep() {
return &washcity_plugin_descriptor;
}

View File

@@ -1315,4 +1315,39 @@ CE99FBC8BD26
# Volgograd (Russia) Volna transport cards keys
2B787A063D5D
D37C8F1793F7
D37C8F1793F7
# Bandai Namco Passport [fka Banapassport] / Sega Aime Card
6090D00632F5
019761AA8082
574343467632
A99164400748
62742819AD7C
CC5075E42BA1
B9DF35A0814C
8AF9C718F23D
58CD5C3673CB
FC80E88EB88C
7A3CDAD7C023
30424C029001
024E4E44001F
ECBBFA57C6AD
4757698143BD
1D30972E6485
F8526D1A8D6D
1300EC8C7E80
F80A65A87FFA
DEB06ED4AF8E
4AD96BF28190
000390014D41
0800F9917CB0
730050555253
4146D4A956C4
131157FBB126
E69DD9015A43
337237F254D5
9A8389F32FBF
7B8FB4A7100B
C8382A233993
7B304F2A12A6
FC9418BF788B