mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-26 14:51:52 +03:00
Refactor response decoder
Read transactions history
This commit is contained in:
parent
39055ff701
commit
4b786fb77e
@ -60,6 +60,56 @@ void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
|
||||
furi_string_cat_printf(str, "\n");
|
||||
}
|
||||
|
||||
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
|
||||
const uint8_t len = apl->active_tr;
|
||||
if(!len) {
|
||||
return;
|
||||
}
|
||||
furi_string_cat_printf(str, "Transactions:\n");
|
||||
for(int i = 0; i < len; i++) {
|
||||
if(!apl->trans[i].amount) continue;
|
||||
uint8_t* a = (uint8_t*)&apl->trans[i].amount;
|
||||
furi_string_cat_printf(str, "%d: ", apl->trans[i].atc);
|
||||
bool top = true;
|
||||
for(int x = 0; x < 6; x++) {
|
||||
if(x == 5) {
|
||||
furi_string_cat_printf(str, ".%02X", a[x]);
|
||||
break;
|
||||
}
|
||||
if(a[x]) {
|
||||
if(top) {
|
||||
furi_string_cat_printf(str, "%X", a[x]);
|
||||
top = false;
|
||||
} else {
|
||||
furi_string_cat_printf(str, "%02X", a[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO to string
|
||||
furi_string_cat_printf(str, " %x\n", apl->trans[i].currency);
|
||||
|
||||
// TODO to string
|
||||
if(apl->trans[i].country)
|
||||
furi_string_cat_printf(str, "country: %x\n", apl->trans[i].country);
|
||||
|
||||
if(apl->trans[i].time)
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"%02lx:%02lx:%02lx ",
|
||||
apl->trans[i].time & 0xff,
|
||||
(apl->trans[i].time >> 8) & 0xff,
|
||||
apl->trans[i].time >> 16);
|
||||
if(apl->trans[i].date)
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"%02lx/%02lx/%02lx\n",
|
||||
apl->trans[i].date >> 16,
|
||||
(apl->trans[i].date >> 8) & 0xff,
|
||||
apl->trans[i].date & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
|
||||
nfc_render_emv_application(&data->emv_application, str);
|
||||
//nfc_render_emv_transactions(&data->emv_application, str);
|
||||
}
|
||||
|
@ -22,3 +22,5 @@ void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str);
|
||||
void nfc_render_emv_country(const EmvApplication* apl, FuriString* str);
|
||||
|
||||
void nfc_render_emv_currency(const EmvApplication* apl, FuriString* str);
|
||||
|
||||
void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str);
|
@ -15,6 +15,15 @@ extern "C" {
|
||||
#define EMV_TAG_CARD_NAME 0x50
|
||||
#define EMV_TAG_FCI 0xBF0C
|
||||
#define EMV_TAG_LOG_ENTRY 0x9F4D
|
||||
#define EMV_TAG_LOG_FMT 0x9F4F
|
||||
|
||||
#define EMV_TAG_ATC 0x9F36
|
||||
#define EMV_TAG_LOG_AMOUNT 0x9F02
|
||||
#define EMV_TAG_LOG_COUNTRY 0x9F1A
|
||||
#define EMV_TAG_LOG_CURRENCY 0x5F2A
|
||||
#define EMV_TAG_LOG_DATE 0x9A
|
||||
#define EMV_TAG_LOG_TIME 0x9F21
|
||||
|
||||
#define EMV_TAG_TRACK_1_EQUIV 0x56
|
||||
#define EMV_TAG_TRACK_2_EQUIV 0x57
|
||||
#define EMV_TAG_PAN 0x5A
|
||||
@ -39,9 +48,22 @@ typedef struct {
|
||||
uint8_t data[MAX_APDU_LEN];
|
||||
} APDU;
|
||||
|
||||
typedef struct {
|
||||
uint16_t atc;
|
||||
uint64_t amount;
|
||||
uint16_t country;
|
||||
uint16_t currency;
|
||||
uint32_t date;
|
||||
uint32_t time;
|
||||
} Transaction;
|
||||
|
||||
typedef struct {
|
||||
uint8_t log_sfi;
|
||||
uint8_t log_records;
|
||||
uint8_t log_fmt[50];
|
||||
uint8_t log_fmt_len;
|
||||
uint8_t active_tr;
|
||||
Transaction trans[16];
|
||||
uint8_t priority;
|
||||
uint8_t aid[16];
|
||||
uint8_t aid_len;
|
||||
|
@ -111,11 +111,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance)
|
||||
}
|
||||
|
||||
static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
|
||||
instance->error = emv_poller_read_files(instance);
|
||||
instance->error = emv_poller_read_afl(instance);
|
||||
|
||||
if(instance->error == EmvErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read files success");
|
||||
instance->state = EmvPollerStateReadSuccess;
|
||||
if(instance->data->emv_application.log_sfi)
|
||||
instance->state = EmvPollerStateReadLogs;
|
||||
else
|
||||
instance->state = EmvPollerStateReadSuccess;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read files");
|
||||
instance->state = EmvPollerStateReadFailed;
|
||||
@ -124,6 +127,19 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand emv_poller_handler_read_logs(EmvPoller* instance) {
|
||||
instance->error = emv_poller_read_log_entry(instance);
|
||||
|
||||
if(instance->error == EmvErrorNone) {
|
||||
FURI_LOG_D(TAG, "Log entries had been read");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "No log entry");
|
||||
}
|
||||
|
||||
instance->state = EmvPollerStateReadSuccess;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand emv_poller_handler_read_fail(EmvPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read failed");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
@ -147,6 +163,7 @@ static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = {
|
||||
[EmvPollerStateSelectApplication] = emv_poller_handler_select_application,
|
||||
[EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options,
|
||||
[EmvPollerStateReadFiles] = emv_poller_handler_read_files,
|
||||
[EmvPollerStateReadLogs] = emv_poller_handler_read_logs,
|
||||
[EmvPollerStateReadFailed] = emv_poller_handler_read_fail,
|
||||
[EmvPollerStateReadSuccess] = emv_poller_handler_read_success,
|
||||
};
|
||||
|
@ -46,7 +46,9 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance);
|
||||
|
||||
EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num);
|
||||
|
||||
EmvError emv_poller_read_files(EmvPoller* instance);
|
||||
EmvError emv_poller_read_afl(EmvPoller* instance);
|
||||
|
||||
EmvError emv_poller_read_log_entry(EmvPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -109,179 +109,277 @@ static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) {
|
||||
return dest->size;
|
||||
}
|
||||
|
||||
static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplication* app) {
|
||||
uint16_t i = 0;
|
||||
uint16_t tag = 0, first_byte = 0;
|
||||
uint16_t tlen = 0;
|
||||
static bool
|
||||
emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) {
|
||||
uint8_t i = 0;
|
||||
bool success = false;
|
||||
|
||||
switch(tag) {
|
||||
case EMV_TAG_LOG_FMT:
|
||||
furi_check(tlen < sizeof(app->log_fmt));
|
||||
memcpy(app->log_fmt, &buff[i], tlen);
|
||||
app->log_fmt_len = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_LOG_FMT %X: len %d", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_GPO_FMT1:
|
||||
// skip AIP
|
||||
i += 2;
|
||||
tlen -= 2;
|
||||
furi_check(tlen < sizeof(app->afl.data));
|
||||
memcpy(app->afl.data, &buff[i], tlen);
|
||||
app->afl.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag);
|
||||
break;
|
||||
case EMV_TAG_AID:
|
||||
app->aid_len = tlen;
|
||||
memcpy(app->aid, &buff[i], tlen);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag);
|
||||
for(size_t x = 0; x < tlen; x++) {
|
||||
FURI_LOG_RAW_T("%02X ", app->aid[x]);
|
||||
}
|
||||
FURI_LOG_RAW_T("\r\n");
|
||||
break;
|
||||
case EMV_TAG_PRIORITY:
|
||||
memcpy(&app->priority, &buff[i], tlen);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
|
||||
break;
|
||||
case EMV_TAG_CARD_NAME:
|
||||
memcpy(app->name, &buff[i], tlen);
|
||||
app->name[tlen] = '\0';
|
||||
app->name_found = true;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
|
||||
break;
|
||||
case EMV_TAG_PDOL:
|
||||
memcpy(app->pdol.data, &buff[i], tlen);
|
||||
app->pdol.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_AFL:
|
||||
memcpy(app->afl.data, &buff[i], tlen);
|
||||
app->afl.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
// Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf
|
||||
case EMV_TAG_TRACK_1_EQUIV: {
|
||||
// Contain PAN and expire date
|
||||
char track_1_equiv[80];
|
||||
memcpy(track_1_equiv, &buff[i], tlen);
|
||||
track_1_equiv[tlen] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_TRACK_2_DATA:
|
||||
case EMV_TAG_TRACK_2_EQUIV: {
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag);
|
||||
// 0xD0 delimits PAN from expiry (YYMM)
|
||||
for(int x = 1; x < tlen; x++) {
|
||||
if(buff[i + x + 1] > 0xD0) {
|
||||
memcpy(app->pan, &buff[i], x + 1);
|
||||
app->pan_len = x + 1;
|
||||
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 4-bit to ASCII representation
|
||||
char track_2_equiv[41];
|
||||
uint8_t track_2_equiv_len = 0;
|
||||
for(int x = 0; x < tlen; x++) {
|
||||
char top = (buff[i + x] >> 4) + '0';
|
||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||
track_2_equiv[x * 2] = top;
|
||||
track_2_equiv_len++;
|
||||
if(top == '?') break;
|
||||
track_2_equiv[x * 2 + 1] = bottom;
|
||||
track_2_equiv_len++;
|
||||
if(bottom == '?') break;
|
||||
}
|
||||
track_2_equiv[track_2_equiv_len] = '\0';
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_PAN:
|
||||
memcpy(app->pan, &buff[i], tlen);
|
||||
app->pan_len = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
|
||||
break;
|
||||
case EMV_TAG_EXP_DATE:
|
||||
app->exp_year = buff[i];
|
||||
app->exp_month = buff[i + 1];
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_CURRENCY_CODE:
|
||||
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_COUNTRY_CODE:
|
||||
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_LOG_ENTRY:
|
||||
app->log_sfi = buff[i];
|
||||
app->log_records = buff[i + 1];
|
||||
success = true;
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d",
|
||||
tag,
|
||||
app->log_sfi,
|
||||
app->log_records);
|
||||
break;
|
||||
case EMV_TAG_ATC:
|
||||
app->trans[app->active_tr].atc = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_LOG_AMOUNT:
|
||||
memcpy(&app->trans[app->active_tr].amount, &buff[i], tlen);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_LOG_COUNTRY:
|
||||
app->trans[app->active_tr].country = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_LOG_CURRENCY:
|
||||
app->trans[app->active_tr].currency = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_LOG_DATE:
|
||||
memcpy(&app->trans[app->active_tr].date, &buff[i], tlen);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_LOG_TIME:
|
||||
memcpy(&app->trans[app->active_tr].time, &buff[i], tlen);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool emv_response_error(const uint8_t* buff, uint16_t len) {
|
||||
uint8_t i = 0;
|
||||
uint8_t first_byte = 0;
|
||||
bool error = true;
|
||||
|
||||
first_byte = buff[i];
|
||||
|
||||
if((len == 2) && ((first_byte >> 4) == 6)) {
|
||||
switch(buff[i]) {
|
||||
case EMV_TAG_RESP_BUF_SIZE:
|
||||
FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]);
|
||||
// Need to request SFI again with this length value
|
||||
return error;
|
||||
case EMV_TAG_RESP_BYTES_AVAILABLE:
|
||||
FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]);
|
||||
// Need to request one more time
|
||||
return error;
|
||||
|
||||
default:
|
||||
FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
emv_parse_tag(const uint8_t* buff, uint16_t len, uint16_t* t, uint8_t* tl, uint8_t* off) {
|
||||
uint8_t i = *off;
|
||||
uint16_t tag = 0;
|
||||
uint8_t first_byte = 0;
|
||||
uint8_t tlen = 0;
|
||||
bool success = false;
|
||||
|
||||
first_byte = buff[i];
|
||||
|
||||
if(emv_response_error(buff, len)) return success;
|
||||
|
||||
if((first_byte & 31) == 31) { // 2-byte tag
|
||||
tag = buff[i] << 8 | buff[i + 1];
|
||||
i++;
|
||||
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
|
||||
} else {
|
||||
tag = buff[i];
|
||||
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
|
||||
}
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
if((tlen & 128) == 128) { // long length value
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
|
||||
} else {
|
||||
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
|
||||
}
|
||||
i++;
|
||||
|
||||
*off = i;
|
||||
*t = tag;
|
||||
*tl = tlen;
|
||||
success = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool emv_decode_tl(
|
||||
const uint8_t* buff,
|
||||
uint16_t len,
|
||||
const uint8_t* fmt,
|
||||
uint8_t fmt_len,
|
||||
EmvApplication* app) {
|
||||
uint8_t i = 0;
|
||||
uint8_t f = 0;
|
||||
uint16_t tag = 0;
|
||||
uint8_t tlen = 0;
|
||||
bool success = false;
|
||||
|
||||
if(emv_response_error(buff, len)) return success;
|
||||
|
||||
while(f < fmt_len && i < len) {
|
||||
success = emv_parse_tag(fmt, fmt_len, &tag, &tlen, &f);
|
||||
if(!success) return success;
|
||||
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
|
||||
i += tlen;
|
||||
}
|
||||
success = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool emv_decode_response_tlv(const uint8_t* buff, uint8_t len, EmvApplication* app) {
|
||||
uint8_t i = 0;
|
||||
uint16_t tag = 0;
|
||||
uint8_t first_byte = 0;
|
||||
uint8_t tlen = 0;
|
||||
bool success = false;
|
||||
|
||||
while(i < len) {
|
||||
first_byte = buff[i];
|
||||
|
||||
if((len == 2) && ((first_byte >> 4) == 6)) {
|
||||
switch(buff[i]) {
|
||||
case EMV_TAG_RESP_BUF_SIZE:
|
||||
FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]);
|
||||
// Need to request SFI again with this length value
|
||||
return success;
|
||||
case EMV_TAG_RESP_BYTES_AVAILABLE:
|
||||
FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]);
|
||||
// Need to request one more time
|
||||
return success;
|
||||
success = emv_parse_tag(buff, len, &tag, &tlen, &i);
|
||||
if(!success) return success;
|
||||
|
||||
default:
|
||||
FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
if((first_byte & 31) == 31) { // 2-byte tag
|
||||
tag = buff[i] << 8 | buff[i + 1];
|
||||
i++;
|
||||
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
|
||||
} else {
|
||||
tag = buff[i];
|
||||
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
|
||||
}
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
if((tlen & 128) == 128) { // long length value
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
|
||||
} else {
|
||||
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
|
||||
}
|
||||
i++;
|
||||
if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse
|
||||
FURI_LOG_T(TAG, "Constructed TLV %x", tag);
|
||||
if(!emv_decode_response(&buff[i], tlen, app)) {
|
||||
if(!emv_decode_response_tlv(&buff[i], tlen, app)) {
|
||||
FURI_LOG_T(TAG, "Failed to decode response for %x", tag);
|
||||
// return false;
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
switch(tag) {
|
||||
case EMV_TAG_GPO_FMT1:
|
||||
// skip AIP
|
||||
i += 2;
|
||||
tlen -= 2;
|
||||
memcpy(app->afl.data, &buff[i], tlen);
|
||||
app->afl.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag);
|
||||
break;
|
||||
case EMV_TAG_AID:
|
||||
app->aid_len = tlen;
|
||||
memcpy(app->aid, &buff[i], tlen);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag);
|
||||
for(size_t x = 0; x < tlen; x++) {
|
||||
FURI_LOG_RAW_T("%02X ", app->aid[x]);
|
||||
}
|
||||
FURI_LOG_RAW_T("\r\n");
|
||||
break;
|
||||
case EMV_TAG_PRIORITY:
|
||||
memcpy(&app->priority, &buff[i], tlen);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
|
||||
break;
|
||||
case EMV_TAG_CARD_NAME:
|
||||
memcpy(app->name, &buff[i], tlen);
|
||||
app->name[tlen] = '\0';
|
||||
app->name_found = true;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
|
||||
break;
|
||||
case EMV_TAG_PDOL:
|
||||
memcpy(app->pdol.data, &buff[i], tlen);
|
||||
app->pdol.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_AFL:
|
||||
memcpy(app->afl.data, &buff[i], tlen);
|
||||
app->afl.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
// Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf
|
||||
case EMV_TAG_TRACK_1_EQUIV: {
|
||||
// Contain PAN and expire date
|
||||
char track_1_equiv[80];
|
||||
memcpy(track_1_equiv, &buff[i], tlen);
|
||||
track_1_equiv[tlen] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_TRACK_2_DATA:
|
||||
case EMV_TAG_TRACK_2_EQUIV: {
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag);
|
||||
// 0xD0 delimits PAN from expiry (YYMM)
|
||||
for(int x = 1; x < tlen; x++) {
|
||||
if(buff[i + x + 1] > 0xD0) {
|
||||
memcpy(app->pan, &buff[i], x + 1);
|
||||
app->pan_len = x + 1;
|
||||
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 4-bit to ASCII representation
|
||||
char track_2_equiv[41];
|
||||
uint8_t track_2_equiv_len = 0;
|
||||
for(int x = 0; x < tlen; x++) {
|
||||
char top = (buff[i + x] >> 4) + '0';
|
||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||
track_2_equiv[x * 2] = top;
|
||||
track_2_equiv_len++;
|
||||
if(top == '?') break;
|
||||
track_2_equiv[x * 2 + 1] = bottom;
|
||||
track_2_equiv_len++;
|
||||
if(bottom == '?') break;
|
||||
}
|
||||
track_2_equiv[track_2_equiv_len] = '\0';
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_PAN:
|
||||
memcpy(app->pan, &buff[i], tlen);
|
||||
app->pan_len = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
|
||||
break;
|
||||
case EMV_TAG_EXP_DATE:
|
||||
app->exp_year = buff[i];
|
||||
app->exp_month = buff[i + 1];
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_CURRENCY_CODE:
|
||||
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_COUNTRY_CODE:
|
||||
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
|
||||
break;
|
||||
case EMV_TAG_LOG_ENTRY:
|
||||
app->log_sfi = buff[i];
|
||||
app->log_records = buff[i + 1];
|
||||
success = true;
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d",
|
||||
tag,
|
||||
app->log_sfi,
|
||||
app->log_records);
|
||||
break;
|
||||
}
|
||||
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
|
||||
}
|
||||
i += tlen;
|
||||
}
|
||||
@ -320,7 +418,7 @@ EmvError emv_poller_select_ppse(EmvPoller* instance) {
|
||||
|
||||
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
if(!emv_decode_response(
|
||||
if(!emv_decode_response_tlv(
|
||||
buff,
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
@ -372,7 +470,7 @@ EmvError emv_poller_select_application(EmvPoller* instance) {
|
||||
|
||||
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
if(!emv_decode_response(
|
||||
if(!emv_decode_response_tlv(
|
||||
buff,
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
@ -424,7 +522,7 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) {
|
||||
|
||||
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
if(!emv_decode_response(
|
||||
if(!emv_decode_response_tlv(
|
||||
buff,
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
@ -466,17 +564,6 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
|
||||
error = emv_process_error(iso14443_4a_error);
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
if(!emv_decode_response(
|
||||
buff,
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
// It's ok while bruteforcing
|
||||
//error = EmvErrorProtocol;
|
||||
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record_num);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
furi_string_free(text);
|
||||
@ -484,7 +571,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
|
||||
return error;
|
||||
}
|
||||
|
||||
EmvError emv_poller_read_files(EmvPoller* instance) {
|
||||
EmvError emv_poller_read_afl(EmvPoller* instance) {
|
||||
EmvError error = EmvErrorNone;
|
||||
|
||||
APDU* afl = &instance->data->emv_application.afl;
|
||||
@ -504,6 +591,14 @@ EmvError emv_poller_read_files(EmvPoller* instance) {
|
||||
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||
if(error != EmvErrorNone) break;
|
||||
|
||||
if(!emv_decode_response_tlv(
|
||||
bit_buffer_get_data(instance->rx_buffer),
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
error = EmvErrorProtocol;
|
||||
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record);
|
||||
}
|
||||
if(instance->data->emv_application.pan_len != 0)
|
||||
return EmvErrorNone; // Card number fetched
|
||||
}
|
||||
@ -511,4 +606,85 @@ EmvError emv_poller_read_files(EmvPoller* instance) {
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
static EmvError emv_poller_get_log_format(EmvPoller* instance) {
|
||||
EmvError error = EmvErrorNone;
|
||||
|
||||
const uint8_t cla_ins[] = {0x80, 0xCA};
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_buffer, cla_ins, sizeof(cla_ins));
|
||||
bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT >> 8);
|
||||
bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT & 0xFF);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x00); //Length
|
||||
|
||||
do {
|
||||
FURI_LOG_D(TAG, "Get log format");
|
||||
|
||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
|
||||
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||
|
||||
emv_trace(instance, "Get log format answer:");
|
||||
|
||||
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to get log format, error %u", iso14443_4a_error);
|
||||
error = emv_process_error(iso14443_4a_error);
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
if(!emv_decode_response_tlv(
|
||||
buff,
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
&instance->data->emv_application)) {
|
||||
error = EmvErrorProtocol;
|
||||
FURI_LOG_E(TAG, "Failed to parse log format");
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
EmvError emv_poller_read_log_entry(EmvPoller* instance) {
|
||||
EmvError error = EmvErrorProtocol;
|
||||
|
||||
uint8_t records = instance->data->emv_application.log_records;
|
||||
if(records == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = emv_poller_get_log_format(instance);
|
||||
if(error != EmvErrorNone) return false;
|
||||
|
||||
FURI_LOG_D(TAG, "Read Transaction logs");
|
||||
|
||||
uint8_t sfi = instance->data->emv_application.log_sfi;
|
||||
uint8_t record_start = 1;
|
||||
uint8_t record_end = records;
|
||||
// Iterate through all records in file
|
||||
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||
error = emv_poller_read_sfi_record(instance, sfi, record);
|
||||
if(error != EmvErrorNone) break;
|
||||
if(!emv_decode_tl(
|
||||
bit_buffer_get_data(instance->rx_buffer),
|
||||
bit_buffer_get_size_bytes(instance->rx_buffer),
|
||||
instance->data->emv_application.log_fmt,
|
||||
instance->data->emv_application.log_fmt_len,
|
||||
&instance->data->emv_application)) {
|
||||
error = EmvErrorProtocol;
|
||||
FURI_LOG_T(TAG, "Failed to parse log SFI 0x%X record %d", sfi, record);
|
||||
break;
|
||||
}
|
||||
|
||||
instance->data->emv_application.active_tr++;
|
||||
furi_check(
|
||||
instance->data->emv_application.active_tr <
|
||||
COUNT_OF(instance->data->emv_application.trans));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ typedef enum {
|
||||
EmvPollerStateSelectApplication,
|
||||
EmvPollerStateGetProcessingOptions,
|
||||
EmvPollerStateReadFiles,
|
||||
EmvPollerStateReadLogs,
|
||||
EmvPollerStateReadFailed,
|
||||
EmvPollerStateReadSuccess,
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,52.0,,
|
||||
Version,+,52.1,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
@ -888,7 +888,8 @@ Function,+,emv_get_uid,const uint8_t*,"const EmvData*, size_t*"
|
||||
Function,+,emv_is_equal,_Bool,"const EmvData*, const EmvData*"
|
||||
Function,+,emv_load,_Bool,"EmvData*, FlipperFormat*, uint32_t"
|
||||
Function,+,emv_poller_get_processing_options,EmvError,EmvPoller*
|
||||
Function,+,emv_poller_read_files,EmvError,EmvPoller*
|
||||
Function,+,emv_poller_read_afl,EmvError,EmvPoller*
|
||||
Function,+,emv_poller_read_log_entry,EmvError,EmvPoller*
|
||||
Function,+,emv_poller_read_sfi_record,EmvError,"EmvPoller*, uint8_t, uint8_t"
|
||||
Function,+,emv_poller_select_application,EmvError,EmvPoller*
|
||||
Function,+,emv_poller_select_ppse,EmvError,EmvPoller*
|
||||
|
|
Loading…
Reference in New Issue
Block a user