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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 65 additions & 42 deletions sam_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ char asn1_log[SEADER_UART_RX_BUF_SIZE] = {0};

uint8_t updateBlock2[] = {RFAL_PICOPASS_CMD_UPDATE, 0x02};

uint8_t ev2_request[] =
uint8_t select_seos_app[] =
{0x00, 0xa4, 0x04, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x04, 0x40, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00};
uint8_t select_desfire_app_no_le[] =
{0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00};
uint8_t FILE_NOT_FOUND[] = {0x6a, 0x82};

void* calloc(size_t count, size_t size) {
Expand Down Expand Up @@ -675,11 +677,18 @@ void seader_capture_sio(BitBuffer* tx_buffer, BitBuffer* rx_buffer, SeaderCreden
}
} else if(credential->type == SeaderCredentialType14A) {
// Desfire EV1 passes SIO in the clear
uint8_t desfire_read[] = {
0x90, 0xbd, 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
if(memcmp(buffer, desfire_read, len) == 0 && rxBuffer[0] == 0x30) {
credential->sio_len =
// The desfire_read command is 13 bytes in total, but we deliberately don't check the read length as newer SAM
// firmware versions read 5 bytes first to determine the length of the SIO from the ASN.1 tag length then do a
// second read with just the required length to skip reading any additional bytes at the end of the file
uint8_t desfire_read[] = {0x90, 0xbd, 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x00};
if(len == 13 && memcmp(buffer, desfire_read, sizeof(desfire_read)) == 0 &&
rxBuffer[0] == 0x30) {
size_t sio_len =
bit_buffer_get_size_bytes(rx_buffer) - 2; // -2 for the APDU response bytes
if(sio_len > sizeof(credential->sio)) {
return;
}
credential->sio_len = sio_len;
memcpy(credential->sio, rxBuffer, credential->sio_len);
}
}
Expand Down Expand Up @@ -744,23 +753,47 @@ void seader_iso14443a_transmit(
SeaderWorker* seader_worker = seader->worker;
SeaderCredential* credential = seader->credential;

BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* tx_buffer =
bit_buffer_alloc(len + 1); // extra byte to allow for appending a Le byte sometimes
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);

do {
if(credential->isDesfire && memcmp(buffer, ev2_request, len) == 0) {
FURI_LOG_I(TAG, "Intercept Desfire EV2 response and return File Not Found");
bit_buffer_append_bytes(rx_buffer, FILE_NOT_FOUND, sizeof(FILE_NOT_FOUND));
bit_buffer_append_bytes(tx_buffer, buffer, len);

} else {
bit_buffer_append_bytes(tx_buffer, buffer, len);
if(seader->credential->isDesfireEV2 && sizeof(select_desfire_app_no_le) == len &&
memcmp(buffer, select_desfire_app_no_le, len) == 0) {
// If a DESFire EV2 card has previously sent a dodgy reply to a SELECT SeosApp
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes you wonder how the hell the readers do it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newer SAM firmware seems to avoid doing the SELECT SeosApp on DESFire cards in the first place, like I can replicate that via Seader even so it's not like we're just talking to the SAM wrong. The question is how it's identifying it, it doesn't have access to the ATQA, and oddly the ATS doesn't seem to matter for the most part either which really just leaves the fact it's a 14443a-4 card, the SAK, and the UID - none of which seems like it would be a reliable indicator, but perhaps the UID is more reliable than expected if you have full NXP datasheets or something.

// future SELECT DESFire commands with no Le byte (Ne == 0) fail with SW 6C00 (Wrong length Le)
// If it has responded with a file not found (ie non-EV2 cards) to the SELECT SeosApp
// then the SELECT DESFire without the Le byte is accepted fine.
// No clue why this happens, but we have to deal with it annoyingly
// We can't just always add the Le byte as this breaks OG D40 cards, so only do it when needed
bit_buffer_append_byte(tx_buffer, 0x00); // Le byte of 0x00 is Ne 256
}

Iso14443_4aError error =
iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
FURI_LOG_W(TAG, "iso14443_4a_poller_send_block error %d", error);
seader_worker->stage = SeaderPollerEventTypeFail;
break;
Iso14443_4aError error =
iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
FURI_LOG_W(TAG, "iso14443_4a_poller_send_block error %d", error);
seader_worker->stage = SeaderPollerEventTypeFail;
break;
}

// if the cAPDU was select seos app and the response starts with 6F228520
// then this is almost certainly a dodgy response from a DESFire EV2 card
// not a Seos card which old SAM firmware don't handle very well, so fake
// a FILD_NOT_FOUND response instead of the real response
if(sizeof(select_seos_app) == len && memcmp(buffer, select_seos_app, len) == 0 &&
bit_buffer_get_size_bytes(rx_buffer) == 38) {
const uint8_t ev2_select_reply_prefix[] = {0x6F, 0x22, 0x85, 0x20};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to put these at the top of the file, but it's not a blocker

const uint8_t* rapdu = bit_buffer_get_data(rx_buffer);
if(memcmp(ev2_select_reply_prefix, rapdu, sizeof(ev2_select_reply_prefix)) == 0) {
FURI_LOG_I(
TAG,
"Intercept DESFire EV2 reply to SELECT SeosApp and return File Not Found");
seader->credential->isDesfireEV2 = true;
bit_buffer_reset(rx_buffer);
bit_buffer_append_bytes(rx_buffer, FILE_NOT_FOUND, sizeof(FILE_NOT_FOUND));
}
}

Expand Down Expand Up @@ -1099,10 +1132,6 @@ bool seader_process_success_response_i(
return processed;
}

bool seader_mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20;
}

NfcCommand seader_worker_card_detect(
Seader* seader,
uint8_t sak,
Expand All @@ -1111,9 +1140,7 @@ NfcCommand seader_worker_card_detect(
uint8_t uid_len,
uint8_t* ats,
uint8_t ats_len) {
UNUSED(ats);
UNUSED(ats_len);

UNUSED(atqa);
SeaderCredential* credential = seader->credential;

CardDetails_t* cardDetails = 0;
Expand All @@ -1122,34 +1149,30 @@ NfcCommand seader_worker_card_detect(

OCTET_STRING_fromBuf(&cardDetails->csn, (const char*)uid, uid_len);
OCTET_STRING_t sak_string = {.buf = &sak, .size = 1};
OCTET_STRING_t atqa_string = {.buf = atqa, .size = 2};
OCTET_STRING_t ats_string = {.buf = ats, .size = ats_len};
uint8_t protocol_bytes[] = {0x00, 0x00};

if(sak != 0 && atqa != NULL) { // type 4
protocol_bytes[1] = FrameProtocol_nfc;
OCTET_STRING_fromBuf(
&cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
cardDetails->sak = &sak_string;
cardDetails->atqa = &atqa_string;
credential->isDesfire = seader_mf_df_check_card_type(atqa[0], atqa[1], sak);
if(credential->isDesfire) {
memcpy(credential->diversifier, uid, uid_len);
credential->diversifier_len = uid_len;
}
} else if(sak != 0 && atqa == NULL) { // MFC
// this won't hold true for Seos cards, but then we won't see the SIO from Seos cards anyway
// so it doesn't really matter
memcpy(credential->diversifier, uid, uid_len);
credential->diversifier_len = uid_len;

if(ats != NULL) { // type 4
protocol_bytes[1] = FrameProtocol_nfc;
OCTET_STRING_fromBuf(
&cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
cardDetails->sak = &sak_string;
// TODO: Update asn1 to change atqa to ats
cardDetails->atqa = &ats_string;
} else if(uid_len == 8) { // picopass
protocol_bytes[1] = FrameProtocol_iclass;
OCTET_STRING_fromBuf(
&cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
memcpy(credential->diversifier, uid, uid_len);
credential->diversifier_len = uid_len;
credential->isDesfire = false;
} else {
FURI_LOG_D(TAG, "Unknown card type");
} else { // MFC
protocol_bytes[1] = FrameProtocol_nfc;
OCTET_STRING_fromBuf(
&cardDetails->protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
cardDetails->sak = &sak_string;
}

seader_send_card_detected(seader, cardDetails);
Expand Down
2 changes: 1 addition & 1 deletion seader_credential.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ typedef struct {
uint8_t diversifier[8];
uint8_t diversifier_len;
uint8_t sio_start_block; // for iClass SE vs iClass SR
bool isDesfire;
bool isDesfireEV2;
SeaderCredentialType type;
SeaderCredentialSaveFormat save_format;
char name[SEADER_CRED_NAME_MAX_LEN + 1];
Expand Down
43 changes: 40 additions & 3 deletions seader_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,49 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
size_t uid_len;
const uint8_t* uid = nfc_device_get_uid(seader->nfc_device, &uid_len);

const Iso14443_3aData* iso14443_3a_data =
nfc_device_get_data(seader->nfc_device, NfcProtocolIso14443_3a);
const Iso14443_4aData* iso14443_4a_data =
nfc_device_get_data(seader->nfc_device, NfcProtocolIso14443_4a);
const Iso14443_3aData* iso14443_3a_data = iso14443_4a_get_base_data(iso14443_4a_data);

uint32_t t1_tk_size = 0;
if(iso14443_4a_data->ats_data.t1_tk != NULL) {
t1_tk_size = simple_array_get_count(iso14443_4a_data->ats_data.t1_tk);
if(t1_tk_size > 0xFF) {
t1_tk_size = 0;
}
}

uint8_t ats_len = 0;
uint8_t* ats = malloc(4 + t1_tk_size);
furi_assert(ats);

if(iso14443_4a_data->ats_data.tl > 1) {
ats[ats_len++] = iso14443_4a_data->ats_data.t0;
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) {
ats[ats_len++] = iso14443_4a_data->ats_data.ta_1;
}
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1) {
ats[ats_len++] = iso14443_4a_data->ats_data.tb_1;
}
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TC1) {
ats[ats_len++] = iso14443_4a_data->ats_data.tc_1;
}

if(t1_tk_size != 0) {
memcpy(
ats + ats_len,
simple_array_cget_data(iso14443_4a_data->ats_data.t1_tk),
t1_tk_size);
ats_len += t1_tk_size;
}
}

uint8_t sak = iso14443_3a_get_sak(iso14443_3a_data);

seader_worker_card_detect(
seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, NULL, 0);
seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, ats, ats_len);

free(ats);

// nfc_set_fdt_poll_fc(event.instance, SEADER_POLLER_MAX_FWT);
furi_thread_set_current_priority(FuriThreadPriorityLowest);
Expand Down
8 changes: 7 additions & 1 deletion seader_worker_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
#include <SamVersion.h>

#define SEADER_POLLER_MAX_FWT (200000U)
#define SEADER_POLLER_MAX_BUFFER_SIZE (255U)
// Maximum basic rAPDU size is 256 bytes of data + 2 byte SW
#define SEADER_POLLER_MAX_BUFFER_SIZE (258U)

// ATS bit definitions
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
#define ISO14443_4A_ATS_T0_TB1 (1U << 5)
#define ISO14443_4A_ATS_T0_TC1 (1U << 6)

struct SeaderWorker {
FuriThread* thread;
Expand Down
Loading