Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions applications/main/nfc/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ App(
sources=["plugins/supported_cards/two_cities.c"],
)

App(
appid="umarsh_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="umarsh_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/umarsh.c"],
)

App(
appid="metromoney_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="metromoney_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/metromoney.c"],
)

App(
appid="nfc_start",
targets=["f7"],
Expand Down
191 changes: 191 additions & 0 deletions applications/main/nfc/plugins/supported_cards/metromoney.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* Parser for Metromoney card (Georgia).
*
* Copyright 2023 Leptoptilos <[email protected]>
*
* 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 "Metromoney"

typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;

static const MfClassicKeyPair metromoney_1k_keys[] = {
{.a = 0x2803BCB0C7E1, .b = 0x4FA9EB49F75E},
{.a = 0x9C616585E26D, .b = 0xD1C71E590D16},
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0x112233445566, .b = 0x361A62F35BC9},
{.a = 0x112233445566, .b = 0x361A62F35BC9},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
};

static bool metromoney_verify(Nfc* nfc) {
bool verified = false;

do {
const uint8_t ticket_sector_number = 1;
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(
metromoney_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 metromoney_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(metromoney_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
nfc_util_num2bytes(metromoney_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 metromoney_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 = 1;

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 != metromoney_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 = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) |
(block_start_ptr[1] << 8) | (block_start_ptr[0]);

uint32_t balance_lari = balance / 100;
uint8_t balance_tetri = balance % 100;

size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]);

furi_string_printf(
parsed_data,
"\e#Metromoney\nCard number: %lu\nBalance: %lu.%02u GEL",
card_number,
balance_lari,
balance_tetri);
parsed = true;
} while(false);

return parsed;
}

/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin metromoney_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = metromoney_verify,
.read = metromoney_read,
.parse = metromoney_parse,
};

/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &metromoney_plugin,
};

/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* metromoney_plugin_ep() {
return &metromoney_plugin_descriptor;
}
87 changes: 60 additions & 27 deletions applications/main/nfc/plugins/supported_cards/social_moscow.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <furi_hal_rtc.h>
#include <core/check.h>

#define TAG "Social Moscow"
#define TAG "Social_Moscow"

typedef struct {
uint64_t a;
Expand All @@ -22,6 +22,24 @@ typedef struct {
uint32_t data_sector;
} SocialMoscowCardConfig;

static const MfClassicKeyPair social_moscow_1k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025},
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057},
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973},
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2},
{.a = 0x40ead80721ce, .b = 0x100533b89331},
{.a = 0x0db5e6523f7c, .b = 0x653a87594079},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}};

static const MfClassicKeyPair social_moscow_4k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
Expand Down Expand Up @@ -511,7 +529,7 @@ void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, ui
bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10);

FURI_LOG_D(TAG, "Transport departament: %x", transport_departament);
FURI_LOG_I(TAG, "Transport departament: %x", transport_departament);

uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4);
if(layout_type == 0xE) {
Expand All @@ -520,7 +538,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {
layout_type = bit_lib_get_bits_16(block->data, 52, 14);
}

FURI_LOG_D(TAG, "Layout type %x", layout_type);
FURI_LOG_I(TAG, "Layout type %x", layout_type);

uint16_t card_view = 0;
uint16_t card_type = 0;
Expand Down Expand Up @@ -1444,8 +1462,10 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) {

static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) {
bool success = true;

if(type == MfClassicType4k) {
if(type == MfClassicType1k) {
config->data_sector = 15;
config->keys = social_moscow_1k_keys;
} else if(type == MfClassicType4k) {
config->data_sector = 15;
config->keys = social_moscow_4k_keys;
} else {
Expand Down Expand Up @@ -1475,7 +1495,7 @@ static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}

FURI_LOG_D(TAG, "Verify success!");
verified = true;
} while(false);

Expand All @@ -1497,7 +1517,7 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
nfc_device_copy_data(device, NfcProtocolMfClassic, data);

do {
MfClassicType type = MfClassicTypeMini;
MfClassicType type = MfClassicType4k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;

Expand Down Expand Up @@ -1537,6 +1557,17 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data
bool parsed = false;

do {
// Verify card type
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, data->type)) break;

// Verify key
const MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);

const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
if(key != cfg.keys[cfg.data_sector].a) break;

uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24);
uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8);
uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40);
Expand All @@ -1549,27 +1580,29 @@ static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data
FuriString* ground_result = furi_string_alloc();
bool result1 = parse_transport_block(&data->block[4], metro_result);
bool result2 = parse_transport_block(&data->block[16], ground_result);
if(result1 || result2) {
furi_string_printf(
parsed_data,
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n\e#Metro\n%s\n\e#Ground\n%s",
card_code,
card_region,
card_number,
card_control,
omc_number,
month,
year,
data->block[60].data[13],
data->block[60].data[14],
furi_string_get_cstr(metro_result),
furi_string_get_cstr(ground_result));
furi_string_free(metro_result);
furi_string_free(ground_result);
parsed = true;
} else {
return false;
furi_string_cat_printf(
parsed_data,
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n",
card_code,
card_region,
card_number,
card_control,
omc_number,
month,
year,
data->block[60].data[13],
data->block[60].data[14]);
if(result1) {
furi_string_cat_printf(
parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result));
}
if(result2) {
furi_string_cat_printf(
parsed_data, "\e#Ground\n%s\n", furi_string_get_cstr(ground_result));
}
furi_string_free(ground_result);
furi_string_free(metro_result);
parsed = result1 || result2;
} while(false);

return parsed;
Expand Down
Loading