mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-02 18:13:33 -07:00
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
#include "crypto1.h"
|
||||
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
#include <furi.h>
|
||||
|
||||
// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
|
||||
|
||||
#define SWAPENDIAN(x) \
|
||||
((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
|
||||
#define LF_POLY_ODD (0x29CE5C)
|
||||
#define LF_POLY_EVEN (0x870804)
|
||||
|
||||
#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
|
||||
|
||||
Crypto1* crypto1_alloc() {
|
||||
Crypto1* instance = malloc(sizeof(Crypto1));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void crypto1_free(Crypto1* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void crypto1_reset(Crypto1* crypto1) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
}
|
||||
|
||||
void crypto1_init(Crypto1* crypto1, uint64_t key) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
for(int8_t i = 47; i > 0; i -= 2) {
|
||||
crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
|
||||
crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t crypto1_filter(uint32_t in) {
|
||||
uint32_t out = 0;
|
||||
out = 0xf22c0 >> (in & 0xf) & 16;
|
||||
out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
|
||||
out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
|
||||
out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
|
||||
out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
|
||||
return FURI_BIT(0xEC57E80A, out);
|
||||
}
|
||||
|
||||
uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = crypto1_filter(crypto1->odd);
|
||||
uint32_t feed = out & (!!is_encrypted);
|
||||
feed ^= !!in;
|
||||
feed ^= LF_POLY_ODD & crypto1->odd;
|
||||
feed ^= LF_POLY_EVEN & crypto1->even;
|
||||
crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
|
||||
|
||||
FURI_SWAP(crypto1->odd, crypto1->even);
|
||||
return out;
|
||||
}
|
||||
|
||||
uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint32_t out = 0;
|
||||
for(uint8_t i = 0; i < 32; i++) {
|
||||
out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n) {
|
||||
SWAPENDIAN(x);
|
||||
while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
|
||||
|
||||
return SWAPENDIAN(x);
|
||||
}
|
||||
|
||||
void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(buff);
|
||||
furi_assert(out);
|
||||
|
||||
size_t bits = bit_buffer_get_size(buff);
|
||||
bit_buffer_set_size(out, bits);
|
||||
const uint8_t* encrypted_data = bit_buffer_get_data(buff);
|
||||
if(bits < 8) {
|
||||
uint8_t decrypted_byte = 0;
|
||||
uint8_t encrypted_byte = encrypted_data[0];
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3;
|
||||
bit_buffer_set_byte(out, 0, decrypted_byte);
|
||||
} else {
|
||||
for(size_t i = 0; i < bits / 8; i++) {
|
||||
uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
|
||||
bit_buffer_set_byte(out, i, decrypted_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(buff);
|
||||
furi_assert(out);
|
||||
|
||||
size_t bits = bit_buffer_get_size(buff);
|
||||
bit_buffer_set_size(out, bits);
|
||||
const uint8_t* plain_data = bit_buffer_get_data(buff);
|
||||
if(bits < 8) {
|
||||
uint8_t encrypted_byte = 0;
|
||||
for(size_t i = 0; i < bits; i++) {
|
||||
encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
|
||||
}
|
||||
bit_buffer_set_byte(out, 0, encrypted_byte);
|
||||
} else {
|
||||
for(size_t i = 0; i < bits / 8; i++) {
|
||||
uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
|
||||
plain_data[i];
|
||||
bool parity_bit =
|
||||
((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crypto1_encrypt_reader_nonce(
|
||||
Crypto1* crypto,
|
||||
uint64_t key,
|
||||
uint32_t cuid,
|
||||
uint8_t* nt,
|
||||
uint8_t* nr,
|
||||
BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(nt);
|
||||
furi_assert(nr);
|
||||
furi_assert(out);
|
||||
|
||||
bit_buffer_set_size_bytes(out, 8);
|
||||
uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t));
|
||||
|
||||
crypto1_init(crypto, key);
|
||||
crypto1_word(crypto, nt_num ^ cuid, 0);
|
||||
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i];
|
||||
bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
|
||||
nr[i] = byte;
|
||||
}
|
||||
|
||||
nt_num = prng_successor(nt_num, 32);
|
||||
for(size_t i = 4; i < 8; i++) {
|
||||
nt_num = prng_successor(nt_num, 8);
|
||||
uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num);
|
||||
bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint32_t odd;
|
||||
uint32_t even;
|
||||
} Crypto1;
|
||||
|
||||
Crypto1* crypto1_alloc();
|
||||
|
||||
void crypto1_free(Crypto1* instance);
|
||||
|
||||
void crypto1_reset(Crypto1* crypto1);
|
||||
|
||||
void crypto1_init(Crypto1* crypto1, uint64_t key);
|
||||
|
||||
uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted);
|
||||
|
||||
uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
|
||||
|
||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
|
||||
|
||||
void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out);
|
||||
|
||||
void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out);
|
||||
|
||||
void crypto1_encrypt_reader_nonce(
|
||||
Crypto1* crypto,
|
||||
uint64_t key,
|
||||
uint32_t cuid,
|
||||
uint8_t* nt,
|
||||
uint8_t* nr,
|
||||
BitBuffer* out);
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,740 @@
|
||||
#include "mf_classic.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
#include <toolbox/hex.h>
|
||||
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
|
||||
#define MF_CLASSIC_PROTOCOL_NAME "Mifare Classic"
|
||||
|
||||
typedef struct {
|
||||
uint8_t sectors_total;
|
||||
uint16_t blocks_total;
|
||||
const char* full_name;
|
||||
const char* type_name;
|
||||
} MfClassicFeatures;
|
||||
|
||||
static const uint32_t mf_classic_data_format_version = 2;
|
||||
|
||||
static const MfClassicFeatures mf_classic_features[MfClassicTypeNum] = {
|
||||
[MfClassicTypeMini] =
|
||||
{
|
||||
.sectors_total = 5,
|
||||
.blocks_total = 20,
|
||||
.full_name = "Mifare Classic Mini 0.3K",
|
||||
.type_name = "MINI",
|
||||
},
|
||||
[MfClassicType1k] =
|
||||
{
|
||||
.sectors_total = 16,
|
||||
.blocks_total = 64,
|
||||
.full_name = "Mifare Classic 1K",
|
||||
.type_name = "1K",
|
||||
},
|
||||
[MfClassicType4k] =
|
||||
{
|
||||
.sectors_total = 40,
|
||||
.blocks_total = 256,
|
||||
.full_name = "Mifare Classic 4K",
|
||||
.type_name = "4K",
|
||||
},
|
||||
};
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_classic = {
|
||||
.protocol_name = MF_CLASSIC_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_classic_alloc,
|
||||
.free = (NfcDeviceFree)mf_classic_free,
|
||||
.reset = (NfcDeviceReset)mf_classic_reset,
|
||||
.copy = (NfcDeviceCopy)mf_classic_copy,
|
||||
.verify = (NfcDeviceVerify)mf_classic_verify,
|
||||
.load = (NfcDeviceLoad)mf_classic_load,
|
||||
.save = (NfcDeviceSave)mf_classic_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_classic_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_classic_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_classic_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_classic_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_classic_get_base_data,
|
||||
};
|
||||
|
||||
MfClassicData* mf_classic_alloc() {
|
||||
MfClassicData* data = malloc(sizeof(MfClassicData));
|
||||
data->iso14443_3a_data = iso14443_3a_alloc();
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_classic_free(MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_free(data->iso14443_3a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_classic_reset(MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_reset(data->iso14443_3a_data);
|
||||
}
|
||||
|
||||
void mf_classic_copy(MfClassicData* data, const MfClassicData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data);
|
||||
for(size_t i = 0; i < COUNT_OF(data->block); i++) {
|
||||
data->block[i] = other->block[i];
|
||||
}
|
||||
for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) {
|
||||
data->block_read_mask[i] = other->block_read_mask[i];
|
||||
}
|
||||
data->type = other->type;
|
||||
data->key_a_mask = other->key_a_mask;
|
||||
data->key_b_mask = other->key_b_mask;
|
||||
}
|
||||
|
||||
bool mf_classic_verify(MfClassicData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal_str(device_type, "Mifare Classic");
|
||||
}
|
||||
|
||||
static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, uint8_t block_num) {
|
||||
furi_string_trim(block_str);
|
||||
MfClassicBlock block_tmp = {};
|
||||
bool is_sector_trailer = mf_classic_is_sector_trailer(block_num);
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint16_t block_unknown_bytes_mask = 0;
|
||||
|
||||
furi_string_trim(block_str);
|
||||
for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
|
||||
char hi = furi_string_get_char(block_str, 3 * i);
|
||||
char low = furi_string_get_char(block_str, 3 * i + 1);
|
||||
uint8_t byte = 0;
|
||||
if(hex_char_to_uint8(hi, low, &byte)) {
|
||||
block_tmp.data[i] = byte;
|
||||
} else {
|
||||
FURI_BIT_SET(block_unknown_bytes_mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
if(block_unknown_bytes_mask != 0xffff) {
|
||||
if(is_sector_trailer) {
|
||||
MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp;
|
||||
// Load Key A
|
||||
// Key A mask 0b0000000000111111 = 0x003f
|
||||
if((block_unknown_bytes_mask & 0x003f) == 0) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a.data, sizeof(MfClassicKey));
|
||||
mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeA, key);
|
||||
}
|
||||
// Load Access Bits
|
||||
// Access bits mask 0b0000001111000000 = 0x03c0
|
||||
if((block_unknown_bytes_mask & 0x03c0) == 0) {
|
||||
mf_classic_set_block_read(data, block_num, &block_tmp);
|
||||
}
|
||||
// Load Key B
|
||||
// Key B mask 0b1111110000000000 = 0xfc00
|
||||
if((block_unknown_bytes_mask & 0xfc00) == 0) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b.data, sizeof(MfClassicKey));
|
||||
mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeB, key);
|
||||
}
|
||||
} else {
|
||||
if(block_unknown_bytes_mask == 0) {
|
||||
mf_classic_set_block_read(data, block_num, &block_tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Read ISO14443_3A data
|
||||
if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break;
|
||||
|
||||
// Read Mifare Classic type
|
||||
if(!flipper_format_read_string(ff, "Mifare Classic type", temp_str)) break;
|
||||
bool type_parsed = false;
|
||||
for(size_t i = 0; i < MfClassicTypeNum; i++) {
|
||||
if(furi_string_equal_str(temp_str, mf_classic_features[i].type_name)) {
|
||||
data->type = i;
|
||||
type_parsed = true;
|
||||
}
|
||||
}
|
||||
if(!type_parsed) break;
|
||||
|
||||
// Read format version
|
||||
uint32_t data_format_version = 0;
|
||||
bool old_format = false;
|
||||
// Read Mifare Classic format version
|
||||
if(!flipper_format_read_uint32(ff, "Data format version", &data_format_version, 1)) {
|
||||
// Load unread sectors with zero keys access for backward compatibility
|
||||
if(!flipper_format_rewind(ff)) break;
|
||||
old_format = true;
|
||||
} else {
|
||||
if(data_format_version < mf_classic_data_format_version) {
|
||||
old_format = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read Mifare Classic blocks
|
||||
bool block_read = true;
|
||||
FuriString* block_str = furi_string_alloc();
|
||||
uint16_t blocks_total = mf_classic_get_total_block_num(data->type);
|
||||
for(size_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_read_string(ff, furi_string_get_cstr(temp_str), block_str)) {
|
||||
block_read = false;
|
||||
break;
|
||||
}
|
||||
mf_classic_parse_block(block_str, data, i);
|
||||
}
|
||||
furi_string_free(block_str);
|
||||
if(!block_read) break;
|
||||
|
||||
// Set keys and blocks as unknown for backward compatibility
|
||||
if(old_format) {
|
||||
data->key_a_mask = 0ULL;
|
||||
data->key_b_mask = 0ULL;
|
||||
memset(data->block_read_mask, 0, sizeof(data->block_read_mask));
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static void
|
||||
mf_classic_set_block_str(FuriString* block_str, const MfClassicData* data, uint8_t block_num) {
|
||||
furi_string_reset(block_str);
|
||||
bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
|
||||
if(is_sec_trailer) {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
// Write key A
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) {
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
// Write Access bytes
|
||||
for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) {
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
// Write key B
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) {
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Write data block
|
||||
for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", data->block[block_num].data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_string_trim(block_str);
|
||||
}
|
||||
|
||||
bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(ff, "Mifare Classic specific data")) break;
|
||||
if(!flipper_format_write_string_cstr(
|
||||
ff, "Mifare Classic type", mf_classic_features[data->type].type_name))
|
||||
break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, "Data format version", &mf_classic_data_format_version, 1))
|
||||
break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff, "Mifare Classic blocks, \'??\' means unknown data"))
|
||||
break;
|
||||
|
||||
uint16_t blocks_total = mf_classic_get_total_block_num(data->type);
|
||||
FuriString* block_str = furi_string_alloc();
|
||||
bool block_saved = true;
|
||||
for(size_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
mf_classic_set_block_str(block_str, data, i);
|
||||
if(!flipper_format_write_string(ff, furi_string_get_cstr(temp_str), block_str)) {
|
||||
block_saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(block_str);
|
||||
if(!block_saved) break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other) {
|
||||
bool is_equal = false;
|
||||
bool data_array_is_equal = true;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break;
|
||||
if(data->type != other->type) break;
|
||||
if(data->key_a_mask != other->key_a_mask) break;
|
||||
if(data->key_b_mask != other->key_b_mask) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) {
|
||||
if(data->block_read_mask[i] != other->block_read_mask[i]) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->block); i++) {
|
||||
if(memcmp(&data->block[i], &other->block[i], sizeof(data->block[i])) != 0) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
is_equal = true;
|
||||
} while(false);
|
||||
|
||||
return is_equal;
|
||||
}
|
||||
|
||||
const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type) {
|
||||
furi_assert(data);
|
||||
furi_assert(data->type < MfClassicTypeNum);
|
||||
|
||||
if(name_type == NfcDeviceNameTypeFull) {
|
||||
return mf_classic_features[data->type].full_name;
|
||||
} else {
|
||||
return mf_classic_features[data->type].type_name;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_3a_data;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
return mf_classic_features[type].sectors_total;
|
||||
}
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
return mf_classic_features[type].blocks_total;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector) {
|
||||
uint8_t block_num = 0;
|
||||
|
||||
if(sector < 32) {
|
||||
block_num = sector * 4 + 3;
|
||||
} else if(sector < 40) {
|
||||
block_num = 32 * 4 + (sector - 32) * 16 + 15;
|
||||
} else {
|
||||
furi_crash("Wrong sector num");
|
||||
}
|
||||
|
||||
return block_num;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) {
|
||||
uint8_t sec_tr_block_num = 0;
|
||||
|
||||
if(block < 128) {
|
||||
sec_tr_block_num = block | 0x03;
|
||||
} else {
|
||||
sec_tr_block_num = block | 0x0f;
|
||||
}
|
||||
|
||||
return sec_tr_block_num;
|
||||
}
|
||||
|
||||
MfClassicSectorTrailer*
|
||||
mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t sec_tr_block = mf_classic_get_sector_trailer_num_by_sector(sector_num);
|
||||
MfClassicSectorTrailer* sec_trailer = (MfClassicSectorTrailer*)&data->block[sec_tr_block];
|
||||
|
||||
return sec_trailer;
|
||||
}
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block) {
|
||||
return block == mf_classic_get_sector_trailer_num_by_block(block);
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block) {
|
||||
uint8_t sector = 0;
|
||||
|
||||
if(block < 128) {
|
||||
sector = (block | 0x03) / 4;
|
||||
} else {
|
||||
sector = 32 + ((block | 0x0f) - 32 * 4) / 16;
|
||||
}
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr) {
|
||||
furi_assert(block);
|
||||
|
||||
uint32_t v = *(uint32_t*)&block->data[0];
|
||||
uint32_t v_inv = *(uint32_t*)&block->data[sizeof(uint32_t)];
|
||||
uint32_t v1 = *(uint32_t*)&block->data[sizeof(uint32_t) * 2];
|
||||
|
||||
bool val_checks =
|
||||
((v == v1) && (v == ~v_inv) && (block->data[12] == (~block->data[13] & 0xFF)) &&
|
||||
(block->data[14] == (~block->data[15] & 0xFF)) && (block->data[12] == block->data[14]));
|
||||
if(value) {
|
||||
*value = (int32_t)v;
|
||||
}
|
||||
if(addr) {
|
||||
*addr = block->data[12];
|
||||
}
|
||||
return val_checks;
|
||||
}
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block) {
|
||||
furi_assert(block);
|
||||
|
||||
uint32_t v_inv = ~((uint32_t)value);
|
||||
|
||||
memcpy(&block->data[0], &value, 4); //-V1086
|
||||
memcpy(&block->data[4], &v_inv, 4); //-V1086
|
||||
memcpy(&block->data[8], &value, 4); //-V1086
|
||||
|
||||
block->data[12] = addr;
|
||||
block->data[13] = ~addr & 0xFF;
|
||||
block->data[14] = addr;
|
||||
block->data[15] = ~addr & 0xFF;
|
||||
}
|
||||
|
||||
bool mf_classic_is_key_found(
|
||||
const MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type) {
|
||||
furi_assert(data);
|
||||
|
||||
bool key_found = false;
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1);
|
||||
}
|
||||
|
||||
return key_found;
|
||||
}
|
||||
|
||||
void mf_classic_set_key_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type,
|
||||
uint64_t key) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t key_arr[6] = {};
|
||||
MfClassicSectorTrailer* sec_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
nfc_util_num2bytes(key, 6, key_arr);
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
memcpy(sec_trailer->key_a.data, key_arr, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(data->key_a_mask, sector_num);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
memcpy(sec_trailer->key_b.data, key_arr, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(data->key_b_mask, sector_num);
|
||||
}
|
||||
}
|
||||
|
||||
void mf_classic_set_key_not_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type) {
|
||||
furi_assert(data);
|
||||
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
FURI_BIT_CLEAR(data->key_a_mask, sector_num);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
FURI_BIT_CLEAR(data->key_b_mask, sector_num);
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) {
|
||||
furi_assert(data);
|
||||
|
||||
return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1);
|
||||
}
|
||||
|
||||
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) {
|
||||
furi_assert(data);
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
memcpy(&data->block[block_num].data[6], &block_data->data[6], 4);
|
||||
} else {
|
||||
memcpy(data->block[block_num].data, block_data->data, MF_CLASSIC_BLOCK_SIZE);
|
||||
}
|
||||
FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
|
||||
furi_assert(sector < 40);
|
||||
|
||||
uint8_t block = 0;
|
||||
if(sector < 32) {
|
||||
block = sector * 4;
|
||||
} else {
|
||||
block = 32 * 4 + (sector - 32) * 16;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
|
||||
furi_assert(sector < 40);
|
||||
return sector < 32 ? 4 : 16;
|
||||
}
|
||||
|
||||
void mf_classic_get_read_sectors_and_keys(
|
||||
const MfClassicData* data,
|
||||
uint8_t* sectors_read,
|
||||
uint8_t* keys_found) {
|
||||
furi_assert(data);
|
||||
furi_assert(sectors_read);
|
||||
furi_assert(keys_found);
|
||||
|
||||
*sectors_read = 0;
|
||||
*keys_found = 0;
|
||||
uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
|
||||
for(size_t i = 0; i < sectors_total; i++) {
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyTypeA)) {
|
||||
*keys_found += 1;
|
||||
}
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyTypeB)) {
|
||||
*keys_found += 1;
|
||||
}
|
||||
uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
|
||||
uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
|
||||
bool blocks_read = true;
|
||||
for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) {
|
||||
blocks_read = mf_classic_is_block_read(data, j);
|
||||
if(!blocks_read) break;
|
||||
}
|
||||
if(blocks_read) {
|
||||
*sectors_read += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_is_card_read(const MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found);
|
||||
bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2);
|
||||
|
||||
return card_read;
|
||||
}
|
||||
|
||||
bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num) {
|
||||
furi_assert(data);
|
||||
|
||||
bool sector_read = false;
|
||||
do {
|
||||
if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) break;
|
||||
if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) break;
|
||||
uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num);
|
||||
uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
|
||||
uint8_t block_read = true;
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
block_read = mf_classic_is_block_read(data, i);
|
||||
if(!block_read) break;
|
||||
}
|
||||
sector_read = block_read;
|
||||
} while(false);
|
||||
|
||||
return sector_read;
|
||||
}
|
||||
|
||||
static bool mf_classic_is_allowed_access_sector_trailer(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
uint8_t* access_bits_arr = sec_tr->access_bits.data;
|
||||
uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) |
|
||||
((access_bits_arr[2] >> 7) & 0x01);
|
||||
|
||||
switch(action) {
|
||||
case MfClassicActionKeyARead: {
|
||||
return false;
|
||||
}
|
||||
case MfClassicActionKeyAWrite:
|
||||
case MfClassicActionKeyBWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03)));
|
||||
}
|
||||
case MfClassicActionKeyBRead: {
|
||||
return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01));
|
||||
}
|
||||
case MfClassicActionACRead: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA) ||
|
||||
(key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01)));
|
||||
}
|
||||
case MfClassicActionACWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05)));
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_classic_is_allowed_access_data_block(
|
||||
MfClassicSectorTrailer* sec_tr,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
furi_assert(sec_tr);
|
||||
|
||||
uint8_t* access_bits_arr = sec_tr->access_bits.data;
|
||||
|
||||
if(block_num == 0 && action == MfClassicActionDataWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector_block = 0;
|
||||
if(block_num <= 128) {
|
||||
sector_block = block_num & 0x03;
|
||||
} else {
|
||||
sector_block = (block_num & 0x0f) / 5;
|
||||
}
|
||||
|
||||
uint8_t AC;
|
||||
switch(sector_block) {
|
||||
case 0x00: {
|
||||
AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) |
|
||||
((access_bits_arr[2] >> 4) & 0x01);
|
||||
break;
|
||||
}
|
||||
case 0x01: {
|
||||
AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) |
|
||||
((access_bits_arr[2] >> 5) & 0x01);
|
||||
break;
|
||||
}
|
||||
case 0x02: {
|
||||
AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) |
|
||||
((access_bits_arr[2] >> 6) & 0x01);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
case MfClassicActionDataRead: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
|
||||
(key_type == MfClassicKeyTypeB && !(AC == 0x07)));
|
||||
}
|
||||
case MfClassicActionDataWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
|
||||
(key_type == MfClassicKeyTypeB &&
|
||||
(AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
|
||||
}
|
||||
case MfClassicActionDataInc: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06)));
|
||||
}
|
||||
case MfClassicActionDataDec: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mf_classic_is_allowed_access(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
furi_assert(data);
|
||||
|
||||
bool access_allowed = false;
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
access_allowed =
|
||||
mf_classic_is_allowed_access_sector_trailer(data, block_num, key_type, action);
|
||||
} else {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
access_allowed =
|
||||
mf_classic_is_allowed_access_data_block(sec_tr, block_num, key_type, action);
|
||||
}
|
||||
|
||||
return access_allowed;
|
||||
}
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num) {
|
||||
furi_assert(sec_tr);
|
||||
|
||||
// Check if key A can write, if it can, it's transport configuration, not data block
|
||||
return !mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) &&
|
||||
(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataInc) ||
|
||||
mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataDec));
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U)
|
||||
#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U)
|
||||
#define MF_CLASSIC_CMD_READ_BLOCK (0x30U)
|
||||
#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U)
|
||||
#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U)
|
||||
#define MF_CLASSIC_CMD_VALUE_INC (0xC1U)
|
||||
#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U)
|
||||
#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U)
|
||||
|
||||
#define MF_CLASSIC_CMD_HALT_MSB (0x50)
|
||||
#define MF_CLASSIC_CMD_HALT_LSB (0x00)
|
||||
#define MF_CLASSIC_CMD_ACK (0x0A)
|
||||
#define MF_CLASSIC_CMD_NACK (0x00)
|
||||
|
||||
#define MF_CLASSIC_TOTAL_SECTORS_MAX (40)
|
||||
#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
|
||||
#define MF_CLASSIC_READ_MASK_SIZE (MF_CLASSIC_TOTAL_BLOCKS_MAX / 32)
|
||||
#define MF_CLASSIC_BLOCK_SIZE (16)
|
||||
#define MF_CLASSIC_KEY_SIZE (6)
|
||||
#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
|
||||
|
||||
#define MF_CLASSIC_NT_SIZE (4)
|
||||
#define MF_CLASSIC_NR_SIZE (4)
|
||||
#define MF_CLASSIC_AR_SIZE (4)
|
||||
#define MF_CLASSIC_AT_SIZE (4)
|
||||
|
||||
typedef enum {
|
||||
MfClassicErrorNone,
|
||||
MfClassicErrorNotPresent,
|
||||
MfClassicErrorProtocol,
|
||||
MfClassicErrorAuth,
|
||||
MfClassicErrorTimeout,
|
||||
} MfClassicError;
|
||||
|
||||
typedef enum {
|
||||
MfClassicTypeMini,
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
|
||||
MfClassicTypeNum,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
MfClassicActionDataRead,
|
||||
MfClassicActionDataWrite,
|
||||
MfClassicActionDataInc,
|
||||
MfClassicActionDataDec,
|
||||
|
||||
MfClassicActionKeyARead,
|
||||
MfClassicActionKeyAWrite,
|
||||
MfClassicActionKeyBRead,
|
||||
MfClassicActionKeyBWrite,
|
||||
MfClassicActionACRead,
|
||||
MfClassicActionACWrite,
|
||||
} MfClassicAction;
|
||||
|
||||
typedef enum {
|
||||
MfClassicValueCommandIncrement,
|
||||
MfClassicValueCommandDecrement,
|
||||
MfClassicValueCommandRestore,
|
||||
|
||||
MfClassicValueCommandInvalid,
|
||||
} MfClassicValueCommand;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_BLOCK_SIZE];
|
||||
} MfClassicBlock;
|
||||
|
||||
typedef enum {
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicKeyTypeB,
|
||||
} MfClassicKeyType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_KEY_SIZE];
|
||||
} MfClassicKey;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_ACCESS_BYTES_SIZE];
|
||||
} MfClassicAccessBits;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_NT_SIZE];
|
||||
} MfClassicNt;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_AT_SIZE];
|
||||
} MfClassicAt;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_NR_SIZE];
|
||||
} MfClassicNr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_AR_SIZE];
|
||||
} MfClassicAr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicNt nt;
|
||||
MfClassicNr nr;
|
||||
MfClassicAr ar;
|
||||
MfClassicAt at;
|
||||
} MfClassicAuthContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicBlock block;
|
||||
struct {
|
||||
MfClassicKey key_a;
|
||||
MfClassicAccessBits access_bits;
|
||||
MfClassicKey key_b;
|
||||
};
|
||||
} MfClassicSectorTrailer;
|
||||
|
||||
typedef struct {
|
||||
uint64_t key_a_mask;
|
||||
MfClassicKey key_a[MF_CLASSIC_TOTAL_SECTORS_MAX];
|
||||
uint64_t key_b_mask;
|
||||
MfClassicKey key_b[MF_CLASSIC_TOTAL_SECTORS_MAX];
|
||||
} MfClassicDeviceKeys;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aData* iso14443_3a_data;
|
||||
MfClassicType type;
|
||||
uint32_t block_read_mask[MF_CLASSIC_READ_MASK_SIZE];
|
||||
uint64_t key_a_mask;
|
||||
uint64_t key_b_mask;
|
||||
MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
|
||||
} MfClassicData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_mf_classic;
|
||||
|
||||
MfClassicData* mf_classic_alloc();
|
||||
|
||||
void mf_classic_free(MfClassicData* data);
|
||||
|
||||
void mf_classic_reset(MfClassicData* data);
|
||||
|
||||
void mf_classic_copy(MfClassicData* data, const MfClassicData* other);
|
||||
|
||||
bool mf_classic_verify(MfClassicData* data, const FuriString* device_type);
|
||||
|
||||
bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other);
|
||||
|
||||
const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len);
|
||||
|
||||
bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data);
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type);
|
||||
|
||||
uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block);
|
||||
|
||||
MfClassicSectorTrailer*
|
||||
mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num);
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block);
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block);
|
||||
|
||||
bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr);
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block);
|
||||
|
||||
bool mf_classic_is_key_found(
|
||||
const MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type);
|
||||
|
||||
void mf_classic_set_key_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type,
|
||||
uint64_t key);
|
||||
|
||||
void mf_classic_set_key_not_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type);
|
||||
|
||||
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num);
|
||||
|
||||
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
|
||||
|
||||
bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num);
|
||||
|
||||
void mf_classic_get_read_sectors_and_keys(
|
||||
const MfClassicData* data,
|
||||
uint8_t* sectors_read,
|
||||
uint8_t* keys_found);
|
||||
|
||||
bool mf_classic_is_card_read(const MfClassicData* data);
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num);
|
||||
|
||||
bool mf_classic_is_allowed_access_data_block(
|
||||
MfClassicSectorTrailer* sec_tr,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action);
|
||||
|
||||
bool mf_classic_is_allowed_access(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,656 @@
|
||||
#include "mf_classic_listener_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "MfClassicListener"
|
||||
|
||||
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
||||
|
||||
typedef MfClassicListenerCommand (
|
||||
*MfClassicListenerCommandHandler)(MfClassicListener* instance, BitBuffer* buf);
|
||||
|
||||
typedef struct {
|
||||
uint8_t cmd_start_byte;
|
||||
size_t cmd_len_bits;
|
||||
size_t command_num;
|
||||
MfClassicListenerCommandHandler* handler;
|
||||
} MfClassicListenerCmd;
|
||||
|
||||
static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) {
|
||||
instance->total_block_num = mf_classic_get_total_block_num(instance->data->type);
|
||||
}
|
||||
|
||||
static void mf_classic_listener_reset_state(MfClassicListener* instance) {
|
||||
crypto1_reset(instance->crypto);
|
||||
memset(&instance->auth_context, 0, sizeof(MfClassicAuthContext));
|
||||
instance->comm_state = MfClassicListenerCommStatePlain;
|
||||
instance->state = MfClassicListenerStateIdle;
|
||||
instance->cmd_in_progress = false;
|
||||
instance->current_cmd_handler_idx = 0;
|
||||
instance->transfer_value = 0;
|
||||
instance->value_cmd = MfClassicValueCommandInvalid;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_halt_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
if(bit_buffer_get_byte(buff, 1) == MF_CLASSIC_CMD_HALT_LSB) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
command = MfClassicListenerCommandSleep;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler(
|
||||
MfClassicListener* instance,
|
||||
MfClassicKeyType key_type,
|
||||
uint8_t block_num) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
do {
|
||||
instance->state = MfClassicListenerStateIdle;
|
||||
|
||||
if(block_num >= instance->total_block_num) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
|
||||
MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(instance->data, sector_num);
|
||||
MfClassicKey* key = (key_type == MfClassicKeyTypeA) ? &sec_tr->key_a : &sec_tr->key_b;
|
||||
uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey));
|
||||
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
||||
|
||||
instance->auth_context.key_type = key_type;
|
||||
instance->auth_context.block_num = block_num;
|
||||
|
||||
furi_hal_random_fill_buf(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
|
||||
crypto1_init(instance->crypto, key_num);
|
||||
if(instance->comm_state == MfClassicListenerCommStatePlain) {
|
||||
crypto1_word(instance->crypto, nt_num ^ cuid, 0);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->auth_context.nt.data,
|
||||
sizeof(MfClassicNt));
|
||||
iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
} else {
|
||||
uint8_t key_stream[4] = {};
|
||||
nfc_util_num2bytes(nt_num ^ cuid, sizeof(uint32_t), key_stream);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
crypto1_encrypt(
|
||||
instance->crypto,
|
||||
key_stream,
|
||||
instance->tx_plain_buffer,
|
||||
instance->tx_encrypted_buffer);
|
||||
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
}
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_key_a_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler(
|
||||
instance, MfClassicKeyTypeA, bit_buffer_get_byte(buff, 1));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_key_b_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler(
|
||||
instance, MfClassicKeyTypeB, bit_buffer_get_byte(buff, 1));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_second_part_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandSilent;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buff) != (sizeof(MfClassicNr) + sizeof(MfClassicAr))) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes_mid(buff, instance->auth_context.nr.data, 0, sizeof(MfClassicNr));
|
||||
bit_buffer_write_bytes_mid(
|
||||
buff, instance->auth_context.ar.data, sizeof(MfClassicNr), sizeof(MfClassicAr));
|
||||
|
||||
if(instance->callback) {
|
||||
instance->mfc_event.type = MfClassicListenerEventTypeAuthContextPartCollected,
|
||||
instance->mfc_event_data.auth_context = instance->auth_context;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
|
||||
uint32_t nr_num = nfc_util_bytes2num(instance->auth_context.nr.data, sizeof(MfClassicNr));
|
||||
uint32_t ar_num = nfc_util_bytes2num(instance->auth_context.ar.data, sizeof(MfClassicAr));
|
||||
|
||||
crypto1_word(instance->crypto, nr_num, 1);
|
||||
uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0);
|
||||
if(secret_poller != prng_successor(nt_num, 64)) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64));
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t at_num = prng_successor(nt_num, 96);
|
||||
nfc_util_num2bytes(at_num, sizeof(uint32_t), instance->auth_context.at.data);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr));
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
|
||||
instance->state = MfClassicListenerStateAuthComplete;
|
||||
instance->comm_state = MfClassicListenerCommStateEncrypted;
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
|
||||
if(instance->callback) {
|
||||
instance->mfc_event.type = MfClassicListenerEventTypeAuthContextFullCollected,
|
||||
instance->mfc_event_data.auth_context = instance->auth_context;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_read_block_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
MfClassicBlock access_block = instance->data->block[block_num];
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
MfClassicSectorTrailer* access_sec_tr = (MfClassicSectorTrailer*)&access_block;
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyARead)) {
|
||||
memset(access_sec_tr->key_a.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyBRead)) {
|
||||
memset(access_sec_tr->key_b.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionACRead)) {
|
||||
memset(access_sec_tr->access_bits.data, 0, sizeof(MfClassicAccessBits));
|
||||
}
|
||||
} else if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionDataRead)) {
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, access_block.data, sizeof(MfClassicBlock));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handler(
|
||||
MfClassicListener* instance,
|
||||
BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(block_num >= instance->total_block_num) break;
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_write_block_second_part_handler(
|
||||
MfClassicListener* instance,
|
||||
BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
size_t buff_size = bit_buffer_get_size_bytes(buff);
|
||||
if(buff_size != sizeof(MfClassicBlock)) break;
|
||||
|
||||
uint8_t block_num = auth_ctx->block_num;
|
||||
MfClassicKeyType key_type = auth_ctx->key_type;
|
||||
MfClassicBlock block = instance->data->block[block_num];
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionKeyAWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionKeyBWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, sec_tr->key_b.data, 10, sizeof(MfClassicKey));
|
||||
}
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionACWrite)) {
|
||||
bit_buffer_write_bytes_mid(
|
||||
buff, sec_tr->access_bits.data, 6, sizeof(MfClassicAccessBits));
|
||||
}
|
||||
} else {
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionDataWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, block.data, 0, sizeof(MfClassicBlock));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->data->block[block_num] = block;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_cmd_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(block_num >= instance->total_block_num) break;
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
uint8_t cmd = bit_buffer_get_byte(buff, 0);
|
||||
MfClassicAction action = MfClassicActionDataDec;
|
||||
if(cmd == MF_CLASSIC_CMD_VALUE_DEC) {
|
||||
instance->value_cmd = MfClassicValueCommandDecrement;
|
||||
} else if(cmd == MF_CLASSIC_CMD_VALUE_INC) {
|
||||
instance->value_cmd = MfClassicValueCommandIncrement;
|
||||
action = MfClassicActionDataInc;
|
||||
} else if(cmd == MF_CLASSIC_CMD_VALUE_RESTORE) {
|
||||
instance->value_cmd = MfClassicValueCommandRestore;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_is_allowed_access(instance->data, block_num, auth_ctx->key_type, action)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_block_to_value(
|
||||
&instance->data->block[block_num], &instance->transfer_value, NULL)) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_dec_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_inc_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_restore_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_data_receive_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
do {
|
||||
if(bit_buffer_get_size_bytes(buff) != 4) break;
|
||||
|
||||
int32_t data;
|
||||
bit_buffer_write_bytes_mid(buff, &data, 0, sizeof(data));
|
||||
|
||||
if(data < 0) {
|
||||
data = -data;
|
||||
}
|
||||
|
||||
if(instance->value_cmd == MfClassicValueCommandDecrement) {
|
||||
data = -data;
|
||||
} else if(instance->value_cmd == MfClassicValueCommandRestore) {
|
||||
data = 0;
|
||||
}
|
||||
|
||||
instance->transfer_value += data;
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandSilent;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_transfer_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buff) != 2) break;
|
||||
if(bit_buffer_get_byte(buff, 0) != MF_CLASSIC_CMD_VALUE_TRANSFER) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionDataDec)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mf_classic_value_to_block(
|
||||
instance->transfer_value, block_num, &instance->data->block[block_num]);
|
||||
instance->transfer_value = 0;
|
||||
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = {
|
||||
mf_classic_listener_halt_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = {
|
||||
mf_classic_listener_auth_key_a_handler,
|
||||
mf_classic_listener_auth_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = {
|
||||
mf_classic_listener_auth_key_b_handler,
|
||||
mf_classic_listener_auth_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = {
|
||||
mf_classic_listener_read_block_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = {
|
||||
mf_classic_listener_write_block_first_part_handler,
|
||||
mf_classic_listener_write_block_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = {
|
||||
mf_classic_listener_value_dec_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = {
|
||||
mf_classic_listener_value_inc_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = {
|
||||
mf_classic_listener_value_restore_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static const MfClassicListenerCmd mf_classic_listener_cmd_handlers[] = {
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_HALT_MSB,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_halt_handlers),
|
||||
.handler = mf_classic_listener_halt_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_A,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_auth_key_a_handlers),
|
||||
.handler = mf_classic_listener_auth_key_a_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_B,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_auth_key_b_handlers),
|
||||
.handler = mf_classic_listener_auth_key_b_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_READ_BLOCK,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_read_block_handlers),
|
||||
.handler = mf_classic_listener_read_block_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_WRITE_BLOCK,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_write_block_handlers),
|
||||
.handler = mf_classic_listener_write_block_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_DEC,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_dec_handlers),
|
||||
.handler = mf_classic_listener_value_dec_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_INC,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_inc_handlers),
|
||||
.handler = mf_classic_listener_value_inc_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_RESTORE,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_restore_handlers),
|
||||
.handler = mf_classic_listener_value_restore_handlers,
|
||||
},
|
||||
};
|
||||
|
||||
static void mf_classic_listener_send_short_frame(MfClassicListener* instance, uint8_t data) {
|
||||
BitBuffer* tx_buffer = instance->tx_plain_buffer;
|
||||
|
||||
bit_buffer_set_size(instance->tx_plain_buffer, 4);
|
||||
bit_buffer_set_byte(instance->tx_plain_buffer, 0, data);
|
||||
if(instance->comm_state == MfClassicListenerCommStateEncrypted) {
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
tx_buffer = instance->tx_encrypted_buffer;
|
||||
}
|
||||
|
||||
iso14443_3a_listener_tx_with_custom_parity(instance->iso14443_3a_listener, tx_buffer);
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicListener* instance = context;
|
||||
Iso14443_3aListenerEvent* iso3_event = event.event_data;
|
||||
BitBuffer* rx_buffer_plain;
|
||||
|
||||
if(iso3_event->type == Iso14443_3aListenerEventTypeFieldOff) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
command = NfcCommandSleep;
|
||||
} else if(
|
||||
(iso3_event->type == Iso14443_3aListenerEventTypeReceivedData) ||
|
||||
(iso3_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame)) {
|
||||
if(instance->comm_state == MfClassicListenerCommStateEncrypted) {
|
||||
if(instance->state == MfClassicListenerStateAuthComplete) {
|
||||
crypto1_decrypt(
|
||||
instance->crypto, iso3_event->data->buffer, instance->rx_plain_buffer);
|
||||
rx_buffer_plain = instance->rx_plain_buffer;
|
||||
if(iso14443_crc_check(Iso14443CrcTypeA, rx_buffer_plain)) {
|
||||
iso14443_crc_trim(rx_buffer_plain);
|
||||
}
|
||||
} else {
|
||||
rx_buffer_plain = iso3_event->data->buffer;
|
||||
}
|
||||
} else {
|
||||
rx_buffer_plain = iso3_event->data->buffer;
|
||||
}
|
||||
|
||||
MfClassicListenerCommand mfc_command = MfClassicListenerCommandNack;
|
||||
if(instance->cmd_in_progress) {
|
||||
mfc_command =
|
||||
mf_classic_listener_cmd_handlers[instance->current_cmd_idx]
|
||||
.handler[instance->current_cmd_handler_idx](instance, rx_buffer_plain);
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(mf_classic_listener_cmd_handlers); i++) {
|
||||
if(bit_buffer_get_size(rx_buffer_plain) !=
|
||||
mf_classic_listener_cmd_handlers[i].cmd_len_bits) {
|
||||
continue;
|
||||
}
|
||||
if(bit_buffer_get_byte(rx_buffer_plain, 0) !=
|
||||
mf_classic_listener_cmd_handlers[i].cmd_start_byte) {
|
||||
continue;
|
||||
}
|
||||
instance->current_cmd_idx = i;
|
||||
instance->current_cmd_handler_idx = 0;
|
||||
mfc_command =
|
||||
mf_classic_listener_cmd_handlers[i].handler[0](instance, rx_buffer_plain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(mfc_command == MfClassicListenerCommandAck) {
|
||||
mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK);
|
||||
} else if(mfc_command == MfClassicListenerCommandNack) {
|
||||
mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK);
|
||||
} else if(mfc_command == MfClassicListenerCommandSilent) {
|
||||
command = NfcCommandReset;
|
||||
} else if(mfc_command == MfClassicListenerCommandSleep) {
|
||||
command = NfcCommandSleep;
|
||||
}
|
||||
} else if(iso3_event->type == Iso14443_3aListenerEventTypeHalted) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
MfClassicListener*
|
||||
mf_classic_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, MfClassicData* data) {
|
||||
MfClassicListener* instance = malloc(sizeof(MfClassicListener));
|
||||
instance->iso14443_3a_listener = iso14443_3a_listener;
|
||||
instance->data = data;
|
||||
mf_classic_listener_prepare_emulation(instance);
|
||||
|
||||
instance->crypto = crypto1_alloc();
|
||||
instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
|
||||
instance->mfc_event.data = &instance->mfc_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolMfClassic;
|
||||
instance->generic_event.event_data = &instance->mfc_event;
|
||||
instance->generic_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void mf_classic_listener_free(MfClassicListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->crypto);
|
||||
furi_assert(instance->rx_plain_buffer);
|
||||
furi_assert(instance->tx_encrypted_buffer);
|
||||
furi_assert(instance->tx_plain_buffer);
|
||||
|
||||
crypto1_free(instance->crypto);
|
||||
bit_buffer_free(instance->rx_plain_buffer);
|
||||
bit_buffer_free(instance->tx_encrypted_buffer);
|
||||
bit_buffer_free(instance->tx_plain_buffer);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void mf_classic_listener_set_callback(
|
||||
MfClassicListener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const MfClassicData* mf_classic_listener_get_data(const MfClassicListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
const NfcListenerBase mf_classic_listener = {
|
||||
.alloc = (NfcListenerAlloc)mf_classic_listener_alloc,
|
||||
.free = (NfcListenerFree)mf_classic_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)mf_classic_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)mf_classic_listener_get_data,
|
||||
.run = (NfcListenerRun)mf_classic_listener_run,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MfClassicListener MfClassicListener;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerEventTypeAuthContextPartCollected,
|
||||
MfClassicListenerEventTypeAuthContextFullCollected,
|
||||
} MfClassicListenerEventType;
|
||||
|
||||
typedef union {
|
||||
MfClassicAuthContext auth_context;
|
||||
} MfClassicListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
MfClassicListenerEventType type;
|
||||
MfClassicListenerEventData* data;
|
||||
} MfClassicListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const NfcListenerBase mf_classic_listener;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic_listener.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h>
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
#include "crypto1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerCommandProcessed,
|
||||
MfClassicListenerCommandAck,
|
||||
MfClassicListenerCommandNack,
|
||||
MfClassicListenerCommandSilent,
|
||||
MfClassicListenerCommandSleep,
|
||||
} MfClassicListenerCommand;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerStateIdle,
|
||||
MfClassicListenerStateAuthComplete,
|
||||
} MfClassicListenerState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerCommStatePlain,
|
||||
MfClassicListenerCommStateEncrypted,
|
||||
} MfClassicListenerCommState;
|
||||
|
||||
struct MfClassicListener {
|
||||
Iso14443_3aListener* iso14443_3a_listener;
|
||||
MfClassicListenerState state;
|
||||
MfClassicListenerCommState comm_state;
|
||||
|
||||
MfClassicData* data;
|
||||
BitBuffer* tx_plain_buffer;
|
||||
BitBuffer* tx_encrypted_buffer;
|
||||
BitBuffer* rx_plain_buffer;
|
||||
|
||||
Crypto1* crypto;
|
||||
MfClassicAuthContext auth_context;
|
||||
|
||||
// Value operation data
|
||||
int32_t transfer_value;
|
||||
MfClassicValueCommand value_cmd;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
MfClassicListenerEvent mfc_event;
|
||||
MfClassicListenerEventData mfc_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
|
||||
bool cmd_in_progress;
|
||||
size_t current_cmd_idx;
|
||||
size_t current_cmd_handler_idx;
|
||||
|
||||
size_t total_block_num;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,951 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfClassicPoller"
|
||||
|
||||
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
||||
|
||||
typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance);
|
||||
|
||||
MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
|
||||
furi_assert(iso14443_3a_poller);
|
||||
|
||||
MfClassicPoller* instance = malloc(sizeof(MfClassicPoller));
|
||||
instance->iso14443_3a_poller = iso14443_3a_poller;
|
||||
instance->data = mf_classic_alloc();
|
||||
instance->crypto = crypto1_alloc();
|
||||
instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
|
||||
instance->mfc_event.data = &instance->mfc_event_data;
|
||||
|
||||
instance->general_event.protocol = NfcProtocolMfClassic;
|
||||
instance->general_event.event_data = &instance->mfc_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void mf_classic_poller_free(MfClassicPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->crypto);
|
||||
furi_assert(instance->tx_plain_buffer);
|
||||
furi_assert(instance->rx_plain_buffer);
|
||||
furi_assert(instance->tx_encrypted_buffer);
|
||||
furi_assert(instance->rx_encrypted_buffer);
|
||||
|
||||
mf_classic_free(instance->data);
|
||||
crypto1_free(instance->crypto);
|
||||
bit_buffer_free(instance->tx_plain_buffer);
|
||||
bit_buffer_free(instance->rx_plain_buffer);
|
||||
bit_buffer_free(instance->tx_encrypted_buffer);
|
||||
bit_buffer_free(instance->rx_encrypted_buffer);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance) {
|
||||
MfClassicPollerEventDataUpdate* data_update = &instance->mfc_event_data.data_update;
|
||||
|
||||
mf_classic_get_read_sectors_and_keys(
|
||||
instance->data, &data_update->sectors_read, &data_update->keys_found);
|
||||
data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate;
|
||||
return instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
static void mf_classic_poller_check_key_b_is_readable(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
do {
|
||||
if(!mf_classic_is_sector_trailer(block_num)) break;
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBRead))
|
||||
break;
|
||||
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data;
|
||||
uint64_t key_b = nfc_util_bytes2num(sec_tr->key_b.data, sizeof(MfClassicKey));
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandReset;
|
||||
|
||||
if(instance->current_type_check == MfClassicType4k) {
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
instance->data->type = MfClassicType4k;
|
||||
instance->state = MfClassicPollerStateStart;
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
FURI_LOG_D(TAG, "4K detected");
|
||||
} else {
|
||||
instance->current_type_check = MfClassicType1k;
|
||||
}
|
||||
} else if(instance->current_type_check == MfClassicType1k) {
|
||||
MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
instance->data->type = MfClassicType1k;
|
||||
FURI_LOG_D(TAG, "1K detected");
|
||||
} else {
|
||||
instance->data->type = MfClassicTypeMini;
|
||||
FURI_LOG_D(TAG, "Mini detected");
|
||||
}
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
instance->state = MfClassicPollerStateStart;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->sectors_total = mf_classic_get_total_sectors_num(instance->data->type);
|
||||
memset(&instance->mode_ctx, 0, sizeof(MfClassicPollerModeContext));
|
||||
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestMode;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) {
|
||||
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) {
|
||||
instance->state = MfClassicPollerStateRequestReadSector;
|
||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) {
|
||||
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
||||
} else {
|
||||
furi_crash("Invalid mode selected");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_sector_trailer(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
if(write_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestSectorTrailer;
|
||||
instance->mfc_event_data.sec_tr_data.sector_num = write_ctx->current_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(instance->mfc_event_data.sec_tr_data.sector_trailer_provided) {
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
memcpy(
|
||||
&write_ctx->sec_tr,
|
||||
&instance->mfc_event_data.sec_tr_data.sector_trailer,
|
||||
sizeof(MfClassicSectorTrailer));
|
||||
write_ctx->current_block =
|
||||
MAX(1, mf_classic_get_first_block_num_of_sector(write_ctx->current_sector));
|
||||
|
||||
} else {
|
||||
write_ctx->current_sector++;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_handler_check_write_conditions(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
MfClassicSectorTrailer* sec_tr = &write_ctx->sec_tr;
|
||||
|
||||
do {
|
||||
// Check last block in sector to write
|
||||
uint8_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(write_ctx->current_sector);
|
||||
if(write_ctx->current_block == sec_tr_block_num) {
|
||||
write_ctx->current_sector++;
|
||||
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check write and read access
|
||||
if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, write_ctx->current_block, MfClassicKeyTypeA, MfClassicActionDataWrite)) {
|
||||
write_ctx->key_type_write = MfClassicKeyTypeA;
|
||||
} else if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataWrite)) {
|
||||
write_ctx->key_type_write = MfClassicKeyTypeB;
|
||||
} else if(mf_classic_is_value_block(sec_tr, write_ctx->current_block)) {
|
||||
write_ctx->is_value_block = true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Not allowed to write block %d", write_ctx->current_block);
|
||||
write_ctx->current_block++;
|
||||
break;
|
||||
}
|
||||
|
||||
if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
write_ctx->key_type_write,
|
||||
MfClassicActionDataRead)) {
|
||||
write_ctx->key_type_read = write_ctx->key_type_write;
|
||||
} else {
|
||||
write_ctx->key_type_read = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
||||
MfClassicKeyTypeB :
|
||||
MfClassicKeyTypeA;
|
||||
if(!mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
write_ctx->key_type_read,
|
||||
MfClassicActionDataRead)) {
|
||||
FURI_LOG_D(TAG, "Not allowed to read block %d", write_ctx->current_block);
|
||||
write_ctx->current_block++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_ctx->need_halt_before_write =
|
||||
(write_ctx->key_type_read != write_ctx->key_type_write);
|
||||
instance->state = MfClassicPollerStateReadBlock;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
MfClassicKey* auth_key = write_ctx->key_type_read == MfClassicKeyTypeA ?
|
||||
&write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
// Authenticate to sector
|
||||
error = mf_classic_async_auth(
|
||||
instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
// Read block from tag
|
||||
error =
|
||||
mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
if(write_ctx->is_value_block) {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateWriteValueBlock;
|
||||
} else {
|
||||
if(write_ctx->need_halt_before_write) {
|
||||
mf_classic_async_halt(instance);
|
||||
}
|
||||
instance->state = MfClassicPollerStateWriteBlock;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
MfClassicKey* auth_key = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
||||
&write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
// Request block to write
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
||||
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
||||
|
||||
// Compare tag and saved block
|
||||
if(memcmp(
|
||||
write_ctx->tag_block.data,
|
||||
instance->mfc_event_data.write_block_data.write_block.data,
|
||||
sizeof(MfClassicBlock)) == 0) {
|
||||
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
||||
break;
|
||||
}
|
||||
|
||||
// Reauth if necessary
|
||||
if(write_ctx->need_halt_before_write) {
|
||||
error = mf_classic_async_auth(
|
||||
instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Failed to auth to block %d for writing", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write block
|
||||
error = mf_classic_async_write_block(
|
||||
instance,
|
||||
write_ctx->current_block,
|
||||
&instance->mfc_event_data.write_block_data.write_block);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
write_ctx->current_block++;
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
do {
|
||||
// Request block to write
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
||||
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
||||
|
||||
// Compare tag and saved block
|
||||
if(memcmp(
|
||||
write_ctx->tag_block.data,
|
||||
instance->mfc_event_data.write_block_data.write_block.data,
|
||||
sizeof(MfClassicBlock)) == 0) {
|
||||
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
||||
break;
|
||||
}
|
||||
|
||||
bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicActionDataInc);
|
||||
bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataInc);
|
||||
bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicActionDataDec);
|
||||
bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataDec);
|
||||
|
||||
int32_t source_value = 0;
|
||||
int32_t target_value = 0;
|
||||
if(!mf_classic_block_to_value(
|
||||
&instance->mfc_event_data.write_block_data.write_block, &source_value, NULL))
|
||||
break;
|
||||
if(!mf_classic_block_to_value(&write_ctx->tag_block, &target_value, NULL)) break;
|
||||
|
||||
MfClassicKeyType auth_key_type = MfClassicKeyTypeA;
|
||||
MfClassicValueCommand value_cmd = MfClassicValueCommandIncrement;
|
||||
int32_t diff = source_value - target_value;
|
||||
if(diff > 0) {
|
||||
if(key_a_inc_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeA;
|
||||
value_cmd = MfClassicValueCommandIncrement;
|
||||
} else if(key_b_inc_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeB;
|
||||
value_cmd = MfClassicValueCommandIncrement;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Unable to increment value block");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(key_a_dec_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeA;
|
||||
value_cmd = MfClassicValueCommandDecrement;
|
||||
diff *= -1;
|
||||
} else if(key_b_dec_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeB;
|
||||
value_cmd = MfClassicValueCommandDecrement;
|
||||
diff *= -1;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Unable to decrement value block");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
|
||||
MfClassicError error =
|
||||
mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_transfer(instance, write_ctx->current_block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
write_ctx->is_value_block = false;
|
||||
write_ctx->current_block++;
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
||||
MfClassicPollerEventDataReadSectorRequest* sec_read =
|
||||
&instance->mfc_event_data.read_sector_request_data;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestReadSector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
if(!sec_read->key_provided) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
sec_read_ctx->current_sector = sec_read->sector_num;
|
||||
sec_read_ctx->key = sec_read->key;
|
||||
sec_read_ctx->key_type = sec_read->key_type;
|
||||
sec_read_ctx->current_block =
|
||||
mf_classic_get_first_block_num_of_sector(sec_read->sector_num);
|
||||
sec_read_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateReadSectorBlocks;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
||||
|
||||
do {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
if(!sec_read_ctx->auth_passed) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_read_ctx->key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Auth to block %d with key %c: %06llx",
|
||||
sec_read_ctx->current_block,
|
||||
sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B',
|
||||
key);
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
sec_read_ctx->current_block,
|
||||
&sec_read_ctx->key,
|
||||
sec_read_ctx->key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
sec_read_ctx->auth_passed = true;
|
||||
if(!mf_classic_is_key_found(
|
||||
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type)) {
|
||||
mf_classic_set_key_found(
|
||||
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type, key);
|
||||
}
|
||||
}
|
||||
if(mf_classic_is_block_read(instance->data, sec_read_ctx->current_block)) break;
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block);
|
||||
MfClassicBlock read_block = {};
|
||||
error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block);
|
||||
if(error == MfClassicErrorNone) {
|
||||
mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block);
|
||||
if(sec_read_ctx->key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(
|
||||
instance, sec_read_ctx->current_block, &read_block);
|
||||
}
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
sec_read_ctx->auth_passed = false;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector);
|
||||
sec_read_ctx->current_block++;
|
||||
if(sec_read_ctx->current_block > sec_tr_num) {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateRequestReadSector;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(instance->mfc_event_data.key_request_data.key_provided) {
|
||||
dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key;
|
||||
instance->state = MfClassicPollerStateAuthKeyA;
|
||||
} else {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateAuthKeyB;
|
||||
} else {
|
||||
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key A found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
dict_attack_ctx->current_block = block;
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateAuthKeyB;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) {
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
} else {
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
} else {
|
||||
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key B found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
dict_attack_ctx->current_block = block;
|
||||
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
dict_attack_ctx->current_sector++;
|
||||
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeNextSector;
|
||||
instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
uint8_t block_num = dict_attack_ctx->current_block;
|
||||
MfClassicBlock block = {};
|
||||
|
||||
do {
|
||||
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
||||
|
||||
if(!dict_attack_ctx->auth_passed) {
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
block_num,
|
||||
&dict_attack_ctx->current_key,
|
||||
dict_attack_ctx->current_key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
||||
error = mf_classic_async_read_block(instance, block_num, &block);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
||||
} else {
|
||||
mf_classic_set_block_read(instance->data, block_num, &block);
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint8_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->current_sector);
|
||||
dict_attack_ctx->current_block++;
|
||||
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
||||
mf_classic_poller_handle_data_update(instance);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
|
||||
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector = dict_attack_ctx->current_sector;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector++;
|
||||
if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
} else {
|
||||
uint8_t block =
|
||||
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key A found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
dict_attack_ctx->current_block = block;
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB)) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
} else {
|
||||
uint8_t block =
|
||||
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key B found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
dict_attack_ctx->current_block = block;
|
||||
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
uint8_t block_num = dict_attack_ctx->current_block;
|
||||
MfClassicBlock block = {};
|
||||
|
||||
do {
|
||||
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
||||
|
||||
if(!dict_attack_ctx->auth_passed) {
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
block_num,
|
||||
&dict_attack_ctx->current_key,
|
||||
dict_attack_ctx->current_key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
||||
error = mf_classic_async_read_block(instance, block_num, &block);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
||||
} else {
|
||||
mf_classic_set_block_read(instance->data, block_num, &block);
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint16_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector);
|
||||
dict_attack_ctx->current_block++;
|
||||
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
|
||||
mf_classic_poller_handle_data_update(instance);
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeSuccess;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_fail(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeFail;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateDetectType;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const MfClassicPollerReadHandler
|
||||
mf_classic_poller_dict_attack_handler[MfClassicPollerStateNum] = {
|
||||
[MfClassicPollerStateDetectType] = mf_classic_poller_handler_detect_type,
|
||||
[MfClassicPollerStateStart] = mf_classic_poller_handler_start,
|
||||
[MfClassicPollerStateRequestSectorTrailer] =
|
||||
mf_classic_poller_handler_request_sector_trailer,
|
||||
[MfClassicPollerStateCheckWriteConditions] = mf_classic_handler_check_write_conditions,
|
||||
[MfClassicPollerStateReadBlock] = mf_classic_poller_handler_read_block,
|
||||
[MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block,
|
||||
[MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block,
|
||||
[MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector,
|
||||
[MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key,
|
||||
[MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector,
|
||||
[MfClassicPollerStateReadSectorBlocks] =
|
||||
mf_classic_poller_handler_request_read_sector_blocks,
|
||||
[MfClassicPollerStateAuthKeyA] = mf_classic_poller_handler_auth_a,
|
||||
[MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b,
|
||||
[MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector,
|
||||
[MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start,
|
||||
[MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a,
|
||||
[MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b,
|
||||
[MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector,
|
||||
[MfClassicPollerStateSuccess] = mf_classic_poller_handler_success,
|
||||
[MfClassicPollerStateFail] = mf_classic_poller_handler_fail,
|
||||
};
|
||||
|
||||
NfcCommand mf_classic_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(context);
|
||||
|
||||
MfClassicPoller* instance = context;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
if(instance->card_state == MfClassicCardStateLost) {
|
||||
instance->card_state = MfClassicCardStateDetected;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeCardDetected;
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
command = mf_classic_poller_dict_attack_handler[instance->state](instance);
|
||||
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
||||
if(instance->card_state == MfClassicCardStateDetected) {
|
||||
instance->card_state = MfClassicCardStateLost;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeCardLost;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
bool mf_classic_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(context);
|
||||
|
||||
Iso14443_3aPoller* iso3_poller = event.instance;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
bool detected = false;
|
||||
const uint8_t auth_cmd[] = {MF_CLASSIC_CMD_AUTH_KEY_A, 0};
|
||||
BitBuffer* tx_buffer = bit_buffer_alloc(COUNT_OF(auth_cmd));
|
||||
bit_buffer_copy_bytes(tx_buffer, auth_cmd, COUNT_OF(auth_cmd));
|
||||
BitBuffer* rx_buffer = bit_buffer_alloc(sizeof(MfClassicNt));
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
|
||||
iso3_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC);
|
||||
if(error == Iso14443_3aErrorWrongCrc) {
|
||||
if(bit_buffer_get_size_bytes(rx_buffer) == sizeof(MfClassicNt)) {
|
||||
detected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bit_buffer_free(tx_buffer);
|
||||
bit_buffer_free(rx_buffer);
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
void mf_classic_poller_set_callback(
|
||||
MfClassicPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const MfClassicData* mf_classic_poller_get_data(const MfClassicPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
const NfcPollerBase mf_classic_poller = {
|
||||
.alloc = (NfcPollerAlloc)mf_classic_poller_alloc,
|
||||
.free = (NfcPollerFree)mf_classic_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)mf_classic_poller_set_callback,
|
||||
.run = (NfcPollerRun)mf_classic_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_classic_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_classic_poller_get_data,
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MfClassicPoller MfClassicPoller;
|
||||
|
||||
typedef enum {
|
||||
// Start event
|
||||
MfClassicPollerEventTypeRequestMode,
|
||||
|
||||
// Read with key cache events
|
||||
MfClassicPollerEventTypeRequestReadSector,
|
||||
|
||||
// Write events
|
||||
MfClassicPollerEventTypeRequestSectorTrailer,
|
||||
MfClassicPollerEventTypeRequestWriteBlock,
|
||||
|
||||
// Dictionary attack events
|
||||
MfClassicPollerEventTypeRequestKey,
|
||||
MfClassicPollerEventTypeNextSector,
|
||||
MfClassicPollerEventTypeDataUpdate,
|
||||
MfClassicPollerEventTypeFoundKeyA,
|
||||
MfClassicPollerEventTypeFoundKeyB,
|
||||
MfClassicPollerEventTypeCardNotDetected,
|
||||
MfClassicPollerEventTypeKeyAttackStart,
|
||||
MfClassicPollerEventTypeKeyAttackStop,
|
||||
MfClassicPollerEventTypeKeyAttackNextSector,
|
||||
|
||||
// Common events
|
||||
MfClassicPollerEventTypeCardDetected,
|
||||
MfClassicPollerEventTypeCardLost,
|
||||
MfClassicPollerEventTypeSuccess,
|
||||
MfClassicPollerEventTypeFail,
|
||||
} MfClassicPollerEventType;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerModeRead,
|
||||
MfClassicPollerModeWrite,
|
||||
MfClassicPollerModeDictAttack,
|
||||
} MfClassicPollerMode;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerMode mode;
|
||||
const MfClassicData* data;
|
||||
} MfClassicPollerEventDataRequestMode;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventDataDictAttackNextSector;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sectors_read;
|
||||
uint8_t keys_found;
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventDataUpdate;
|
||||
|
||||
typedef struct {
|
||||
MfClassicKey key;
|
||||
bool key_provided;
|
||||
} MfClassicPollerEventDataKeyRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sector_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
bool key_provided;
|
||||
} MfClassicPollerEventDataReadSectorRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sector_num;
|
||||
MfClassicBlock sector_trailer;
|
||||
bool sector_trailer_provided;
|
||||
} MfClassicPollerEventDataSectorTrailerRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicBlock write_block;
|
||||
bool write_block_provided;
|
||||
} MfClassicPollerEventDataWriteBlockRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventKeyAttackData;
|
||||
|
||||
typedef union {
|
||||
MfClassicError error;
|
||||
MfClassicPollerEventDataRequestMode poller_mode;
|
||||
MfClassicPollerEventDataDictAttackNextSector next_sector_data;
|
||||
MfClassicPollerEventDataKeyRequest key_request_data;
|
||||
MfClassicPollerEventDataUpdate data_update;
|
||||
MfClassicPollerEventDataReadSectorRequest read_sector_request_data;
|
||||
MfClassicPollerEventKeyAttackData key_attack_data;
|
||||
MfClassicPollerEventDataSectorTrailerRequest sec_tr_data;
|
||||
MfClassicPollerEventDataWriteBlockRequest write_block_data;
|
||||
} MfClassicPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerEventType type;
|
||||
MfClassicPollerEventData* data;
|
||||
} MfClassicPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const NfcPollerBase mf_classic_poller;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,386 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define TAG "MfCLassicPoller"
|
||||
|
||||
MfClassicError mf_classic_process_error(Iso14443_3aError error) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case Iso14443_3aErrorNone:
|
||||
ret = MfClassicErrorNone;
|
||||
break;
|
||||
case Iso14443_3aErrorNotPresent:
|
||||
ret = MfClassicErrorNotPresent;
|
||||
break;
|
||||
case Iso14443_3aErrorColResFailed:
|
||||
case Iso14443_3aErrorCommunication:
|
||||
case Iso14443_3aErrorWrongCrc:
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
case Iso14443_3aErrorTimeout:
|
||||
ret = MfClassicErrorTimeout;
|
||||
break;
|
||||
default:
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_get_nt(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B :
|
||||
MF_CLASSIC_CMD_AUTH_KEY_A;
|
||||
uint8_t auth_cmd[2] = {auth_type, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd));
|
||||
|
||||
error = iso14443_3a_poller_send_standard_frame(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_plain_buffer,
|
||||
instance->rx_plain_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorWrongCrc) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
if(nt) {
|
||||
bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_auth(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
|
||||
MfClassicNt nt = {};
|
||||
ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt);
|
||||
if(ret != MfClassicErrorNone) break;
|
||||
if(data) {
|
||||
data->nt = nt;
|
||||
}
|
||||
|
||||
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
||||
uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey));
|
||||
MfClassicNr nr = {};
|
||||
furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr));
|
||||
|
||||
crypto1_encrypt_reader_nonce(
|
||||
instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer);
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorAuth;
|
||||
}
|
||||
|
||||
crypto1_word(instance->crypto, 0, 0);
|
||||
instance->auth_state = MfClassicAuthStatePassed;
|
||||
|
||||
if(data) {
|
||||
data->nr = nr;
|
||||
const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer);
|
||||
memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr));
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if(ret != MfClassicErrorNone) {
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_halt(MfClassicPoller* instance) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorTimeout) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
instance->auth_state = MfClassicAuthStateIdle;
|
||||
instance->iso14443_3a_poller->state = Iso14443_3aPollerStateIdle;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_read_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t read_block_cmd[2] = {MF_CLASSIC_CMD_READ_BLOCK, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, read_block_cmd, sizeof(read_block_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) !=
|
||||
(sizeof(MfClassicBlock) + 2)) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_plain_buffer)) {
|
||||
FURI_LOG_D(TAG, "CRC error");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
iso14443_crc_trim(instance->rx_plain_buffer);
|
||||
bit_buffer_write_bytes(instance->rx_plain_buffer, data->data, sizeof(MfClassicBlock));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_write_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_value_cmd(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicValueCommand cmd,
|
||||
int32_t data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t cmd_value = 0;
|
||||
if(cmd == MfClassicValueCommandDecrement) {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_DEC;
|
||||
} else if(cmd == MfClassicValueCommandIncrement) {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_INC;
|
||||
} else {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_RESTORE;
|
||||
}
|
||||
uint8_t value_cmd[2] = {cmd_value, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, value_cmd, sizeof(value_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, (uint8_t*)&data, sizeof(data));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
|
||||
// Command processed if tag doesn't respond
|
||||
if(error != Iso14443_3aErrorTimeout) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
ret = MfClassicErrorNone;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t transfer_cmd[2] = {MF_CLASSIC_CMD_VALUE_TRANSFER, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, transfer_cmd, sizeof(transfer_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic_poller.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h>
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
#include "crypto1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_CLASSIC_FWT_FC (60000)
|
||||
|
||||
typedef enum {
|
||||
MfClassicAuthStateIdle,
|
||||
MfClassicAuthStatePassed,
|
||||
} MfClassicAuthState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicCardStateDetected,
|
||||
MfClassicCardStateLost,
|
||||
} MfClassicCardState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerStateDetectType,
|
||||
MfClassicPollerStateStart,
|
||||
|
||||
// Write states
|
||||
MfClassicPollerStateRequestSectorTrailer,
|
||||
MfClassicPollerStateCheckWriteConditions,
|
||||
MfClassicPollerStateReadBlock,
|
||||
MfClassicPollerStateWriteBlock,
|
||||
MfClassicPollerStateWriteValueBlock,
|
||||
|
||||
// Read states
|
||||
MfClassicPollerStateRequestReadSector,
|
||||
MfClassicPollerStateReadSectorBlocks,
|
||||
|
||||
// Dict attack states
|
||||
MfClassicPollerStateNextSector,
|
||||
MfClassicPollerStateRequestKey,
|
||||
MfClassicPollerStateReadSector,
|
||||
MfClassicPollerStateAuthKeyA,
|
||||
MfClassicPollerStateAuthKeyB,
|
||||
MfClassicPollerStateKeyReuseStart,
|
||||
MfClassicPollerStateKeyReuseAuthKeyA,
|
||||
MfClassicPollerStateKeyReuseAuthKeyB,
|
||||
MfClassicPollerStateKeyReuseReadSector,
|
||||
MfClassicPollerStateSuccess,
|
||||
MfClassicPollerStateFail,
|
||||
|
||||
MfClassicPollerStateNum,
|
||||
} MfClassicPollerState;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
MfClassicSectorTrailer sec_tr;
|
||||
uint16_t current_block;
|
||||
bool is_value_block;
|
||||
MfClassicKeyType key_type_read;
|
||||
MfClassicKeyType key_type_write;
|
||||
bool need_halt_before_write;
|
||||
MfClassicBlock tag_block;
|
||||
} MfClassicPollerWriteContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
MfClassicKey current_key;
|
||||
MfClassicKeyType current_key_type;
|
||||
bool auth_passed;
|
||||
uint16_t current_block;
|
||||
uint8_t reuse_key_sector;
|
||||
} MfClassicPollerDictAttackContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
uint16_t current_block;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicKey key;
|
||||
bool auth_passed;
|
||||
} MfClassicPollerReadContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicPollerWriteContext write_ctx;
|
||||
MfClassicPollerDictAttackContext dict_attack_ctx;
|
||||
MfClassicPollerReadContext read_ctx;
|
||||
|
||||
} MfClassicPollerModeContext;
|
||||
|
||||
struct MfClassicPoller {
|
||||
Iso14443_3aPoller* iso14443_3a_poller;
|
||||
|
||||
MfClassicPollerState state;
|
||||
MfClassicAuthState auth_state;
|
||||
MfClassicCardState card_state;
|
||||
|
||||
MfClassicType current_type_check;
|
||||
uint8_t sectors_total;
|
||||
MfClassicPollerModeContext mode_ctx;
|
||||
|
||||
Crypto1* crypto;
|
||||
BitBuffer* tx_plain_buffer;
|
||||
BitBuffer* tx_encrypted_buffer;
|
||||
BitBuffer* rx_plain_buffer;
|
||||
BitBuffer* rx_encrypted_buffer;
|
||||
MfClassicData* data;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
MfClassicPollerEvent mfc_event;
|
||||
MfClassicPollerEventData mfc_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t block;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicNt nt;
|
||||
} MfClassicCollectNtContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicBlock block;
|
||||
} MfClassicReadBlockContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicBlock block;
|
||||
} MfClassicWriteBlockContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
int32_t value;
|
||||
} MfClassicReadValueContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicValueCommand value_cmd;
|
||||
int32_t data;
|
||||
int32_t new_value;
|
||||
} MfClassicChangeValueContext;
|
||||
|
||||
typedef struct {
|
||||
MfClassicDeviceKeys keys;
|
||||
uint8_t current_sector;
|
||||
} MfClassicReadContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicCollectNtContext collect_nt_context;
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicReadBlockContext read_block_context;
|
||||
MfClassicWriteBlockContext write_block_context;
|
||||
MfClassicReadValueContext read_value_context;
|
||||
MfClassicChangeValueContext change_value_context;
|
||||
MfClassicReadContext read_context;
|
||||
} MfClassicPollerContextData;
|
||||
|
||||
MfClassicError mf_classic_process_error(Iso14443_3aError error);
|
||||
|
||||
MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller);
|
||||
|
||||
void mf_classic_poller_free(MfClassicPoller* instance);
|
||||
|
||||
MfClassicError mf_classic_async_get_nt(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt);
|
||||
|
||||
MfClassicError mf_classic_async_auth(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data);
|
||||
|
||||
MfClassicError mf_classic_async_halt(MfClassicPoller* instance);
|
||||
|
||||
MfClassicError
|
||||
mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_async_write_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_async_value_cmd(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicValueCommand cmd,
|
||||
int32_t data);
|
||||
|
||||
MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,524 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfClassicPoller"
|
||||
|
||||
#define MF_CLASSIC_POLLER_COMPLETE_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerCmdTypeCollectNt,
|
||||
MfClassicPollerCmdTypeAuth,
|
||||
MfClassicPollerCmdTypeReadBlock,
|
||||
MfClassicPollerCmdTypeWriteBlock,
|
||||
MfClassicPollerCmdTypeReadValue,
|
||||
MfClassicPollerCmdTypeChangeValue,
|
||||
|
||||
MfClassicPollerCmdTypeNum,
|
||||
} MfClassicPollerCmdType;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerCmdType cmd_type;
|
||||
FuriThreadId thread_id;
|
||||
MfClassicError error;
|
||||
MfClassicPollerContextData data;
|
||||
} MfClassicPollerContext;
|
||||
|
||||
typedef MfClassicError (
|
||||
*MfClassicPollerCmdHandler)(MfClassicPoller* poller, MfClassicPollerContextData* data);
|
||||
|
||||
static MfClassicError mf_classic_poller_collect_nt_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
return mf_classic_async_get_nt(
|
||||
poller,
|
||||
data->collect_nt_context.block,
|
||||
data->collect_nt_context.key_type,
|
||||
&data->collect_nt_context.nt);
|
||||
}
|
||||
|
||||
static MfClassicError
|
||||
mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) {
|
||||
return mf_classic_async_auth(
|
||||
poller,
|
||||
data->auth_context.block_num,
|
||||
&data->auth_context.key,
|
||||
data->auth_context.key_type,
|
||||
&data->auth_context);
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_read_block_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_block_context.block_num,
|
||||
&data->read_block_context.key,
|
||||
data->read_block_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_read_block(
|
||||
poller, data->read_block_context.block_num, &data->read_block_context.block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_write_block_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_block_context.block_num,
|
||||
&data->read_block_context.key,
|
||||
data->read_block_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_write_block(
|
||||
poller, data->write_block_context.block_num, &data->write_block_context.block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_read_value_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_value_context.block_num,
|
||||
&data->read_value_context.key,
|
||||
data->read_value_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
MfClassicBlock block = {};
|
||||
error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) {
|
||||
error = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_change_value_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->change_value_context.block_num,
|
||||
&data->change_value_context.key,
|
||||
data->change_value_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_cmd(
|
||||
poller,
|
||||
data->change_value_context.block_num,
|
||||
data->change_value_context.value_cmd,
|
||||
data->change_value_context.data);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
MfClassicBlock block = {};
|
||||
error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) {
|
||||
error = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicPollerCmdTypeNum] = {
|
||||
[MfClassicPollerCmdTypeCollectNt] = mf_classic_poller_collect_nt_handler,
|
||||
[MfClassicPollerCmdTypeAuth] = mf_classic_poller_auth_handler,
|
||||
[MfClassicPollerCmdTypeReadBlock] = mf_classic_poller_read_block_handler,
|
||||
[MfClassicPollerCmdTypeWriteBlock] = mf_classic_poller_write_block_handler,
|
||||
[MfClassicPollerCmdTypeReadValue] = mf_classic_poller_read_value_handler,
|
||||
[MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler,
|
||||
};
|
||||
|
||||
static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(context);
|
||||
|
||||
MfClassicPollerContext* poller_context = context;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
Iso14443_3aPoller* iso14443_3a_poller = event.instance;
|
||||
MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller);
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type](
|
||||
mfc_poller, &poller_context->data);
|
||||
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
||||
poller_context->error = mf_classic_process_error(iso14443_3a_event->data->error);
|
||||
}
|
||||
|
||||
furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
mf_classic_poller_free(mfc_poller);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerContext* poller_ctx) {
|
||||
furi_assert(poller_ctx->cmd_type < MfClassicPollerCmdTypeNum);
|
||||
|
||||
poller_ctx->thread_id = furi_thread_get_current_id();
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
|
||||
nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx);
|
||||
furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
return poller_ctx->error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_collect_nt(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt) {
|
||||
furi_assert(nfc);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeCollectNt,
|
||||
.data.collect_nt_context.block = block_num,
|
||||
.data.collect_nt_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
if(nt) {
|
||||
*nt = poller_context.data.collect_nt_context.nt;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_auth(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeAuth,
|
||||
.data.auth_context.block_num = block_num,
|
||||
.data.auth_context.key = *key,
|
||||
.data.auth_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
if(data) {
|
||||
*data = poller_context.data.auth_context;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_read_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeReadBlock,
|
||||
.data.read_block_context.block_num = block_num,
|
||||
.data.read_block_context.key = *key,
|
||||
.data.read_block_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*data = poller_context.data.read_block_context.block;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_write_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeWriteBlock,
|
||||
.data.write_block_context.block_num = block_num,
|
||||
.data.write_block_context.key = *key,
|
||||
.data.write_block_context.key_type = key_type,
|
||||
.data.write_block_context.block = *data,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_read_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t* value) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(value);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeReadValue,
|
||||
.data.write_block_context.block_num = block_num,
|
||||
.data.write_block_context.key = *key,
|
||||
.data.write_block_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*value = poller_context.data.read_value_context.value;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_change_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t data,
|
||||
int32_t* new_value) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(new_value);
|
||||
|
||||
MfClassicValueCommand command = MfClassicValueCommandRestore;
|
||||
int32_t command_data = 0;
|
||||
if(data > 0) {
|
||||
command = MfClassicValueCommandIncrement;
|
||||
command_data = data;
|
||||
} else if(data < 0) {
|
||||
command = MfClassicValueCommandDecrement;
|
||||
command_data = -data;
|
||||
}
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeChangeValue,
|
||||
.data.change_value_context.block_num = block_num,
|
||||
.data.change_value_context.key = *key,
|
||||
.data.change_value_context.key_type = key_type,
|
||||
.data.change_value_context.value_cmd = command,
|
||||
.data.change_value_context.data = command_data,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*new_value = poller_context.data.change_value_context.new_value;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool mf_classic_poller_read_get_next_key(
|
||||
MfClassicReadContext* read_ctx,
|
||||
uint8_t* sector_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType* key_type) {
|
||||
bool next_key_found = false;
|
||||
|
||||
for(uint8_t i = read_ctx->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
|
||||
if(FURI_BIT(read_ctx->keys.key_a_mask, i)) {
|
||||
FURI_BIT_CLEAR(read_ctx->keys.key_a_mask, i);
|
||||
*key = read_ctx->keys.key_a[i];
|
||||
*key_type = MfClassicKeyTypeA;
|
||||
*sector_num = i;
|
||||
|
||||
next_key_found = true;
|
||||
break;
|
||||
}
|
||||
if(FURI_BIT(read_ctx->keys.key_b_mask, i)) {
|
||||
FURI_BIT_CLEAR(read_ctx->keys.key_b_mask, i);
|
||||
*key = read_ctx->keys.key_b[i];
|
||||
*key_type = MfClassicKeyTypeB;
|
||||
*sector_num = i;
|
||||
|
||||
next_key_found = true;
|
||||
read_ctx->current_sector = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next_key_found;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolMfClassic);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerContext* poller_context = context;
|
||||
MfClassicPollerEvent* mfc_event = event.event_data;
|
||||
|
||||
if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
|
||||
poller_context->error = MfClassicErrorNotPresent;
|
||||
command = NfcCommandStop;
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
|
||||
mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
|
||||
MfClassicPollerEventDataReadSectorRequest* req_data =
|
||||
&mfc_event->data->read_sector_request_data;
|
||||
MfClassicKey key = {};
|
||||
MfClassicKeyType key_type = MfClassicKeyTypeA;
|
||||
uint8_t sector_num = 0;
|
||||
if(mf_classic_poller_read_get_next_key(
|
||||
&poller_context->data.read_context, §or_num, &key, &key_type)) {
|
||||
req_data->sector_num = sector_num;
|
||||
req_data->key = key;
|
||||
req_data->key_type = key_type;
|
||||
req_data->key_provided = true;
|
||||
} else {
|
||||
req_data->key_provided = false;
|
||||
}
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
if(command == NfcCommandStop) {
|
||||
furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
MfClassicError
|
||||
mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(keys);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
MfClassicPollerContext poller_context = {};
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
poller_context.data.read_context.keys = *keys;
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic);
|
||||
nfc_poller_start(poller, mf_classic_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
|
||||
if(poller_context.error != MfClassicErrorNone) {
|
||||
error = poller_context.error;
|
||||
} else {
|
||||
const MfClassicData* mfc_data = nfc_poller_get_data(poller);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
|
||||
mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found);
|
||||
if((sectors_read > 0) || (keys_found > 0)) {
|
||||
mf_classic_copy(data, mfc_data);
|
||||
} else {
|
||||
error = MfClassicErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
nfc_poller_free(poller);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(type);
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
const uint8_t mf_classic_verify_block[MfClassicTypeNum] = {
|
||||
[MfClassicTypeMini] = 0,
|
||||
[MfClassicType1k] = 62,
|
||||
[MfClassicType4k] = 254,
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) {
|
||||
error = mf_classic_poller_collect_nt(
|
||||
nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
*type = MfClassicTypeNum - i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
MfClassicError mf_classic_poller_collect_nt(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt);
|
||||
|
||||
MfClassicError mf_classic_poller_auth(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data);
|
||||
|
||||
MfClassicError mf_classic_poller_read_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_poller_write_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_poller_read_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t* value);
|
||||
|
||||
MfClassicError mf_classic_poller_change_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t data,
|
||||
int32_t* new_value);
|
||||
|
||||
MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type);
|
||||
|
||||
MfClassicError
|
||||
mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user