Skip to content

Commit

Permalink
Add support for EMV card reading
Browse files Browse the repository at this point in the history
Only MasterCard tested. Visa support will be added in future. This feature require an Elechouse PN532, it doesn't work with LiLyGo T-Embed CC1101
  • Loading branch information
andreock committed Jan 26, 2025
1 parent e1f65e5 commit 0f8c2f0
Show file tree
Hide file tree
Showing 13 changed files with 436 additions and 11 deletions.
31 changes: 30 additions & 1 deletion lib/UI/navigation/NFC/NFCNavigation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "pages/NFC/NFCPollingWaitingPage.hpp"
#include "pages/NFC/NFCWriteResultPage.hpp"
#include "pages/NFC/NFCEmulateTagPage.hpp"
#include "pages/NFC/NFCEMVReadPage.hpp"
#include "posixsd.hpp"
#include "sdcard_helper.hpp"

Expand All @@ -46,6 +47,7 @@ static NFCFelicaPollingResultPage *nfc_felica_polling_result_page = nullptr;
static FileBrowserPage *nfc_dump_file_browser_page = nullptr;
static NFCWriteResultPage *nfc_write_result_page = nullptr;
static NFCEmulateTagPage *nfc_emulate_tag_page = nullptr;
static NFCEMVReadPage *nfc_emv_read_page = nullptr;

std::list<std::string> nfc_dumps_files; // NFC Dumps files

Expand All @@ -57,7 +59,7 @@ std::list<std::string> nfc_dumps_files; // NFC Dumps files

void goto_nfc_gui() {
gui->reset();
nfc_main_page = new NFCMainPage(2, 0, 1, gui->get_screen());
nfc_main_page = new NFCMainPage(3, 0, 1, gui->get_screen());
gui->set_current_page(nfc_main_page);
}

Expand Down Expand Up @@ -94,6 +96,8 @@ void nfc_cleanup() {
if (!nfc_dumps_files.empty()) {
nfc_dumps_files.clear();
}
nfc_emulate_tag_page = nullptr;
nfc_emv_read_page = nullptr;
}

bool emulating = false;
Expand All @@ -103,6 +107,8 @@ void goto_home() {
stop_emulate();
emulating = false;
}

destroy_tasks(nfc_attacks);
reset_uid();
reset_felica();
nfc_cleanup();
Expand Down Expand Up @@ -277,6 +283,29 @@ void set_unwritable_sectors(size_t val) {
nfc_write_result_page->set_unwritable_sectors(val);
}

void read_emv_card() {
gui->reset();
nfc_emv_read_page = new NFCEMVReadPage(5, 5, 0, gui->get_screen());
gui->set_current_page(nfc_emv_read_page);
read_emv_card_attack(gui, nfc_attacks);
}

void set_emv_type(String type) {
nfc_emv_read_page->set_card_type(type);
}

void set_emv_pan(String pan) {
nfc_emv_read_page->set_card_pan(pan);
}

void set_emv_issue_date(String issuedate) {
nfc_emv_read_page->set_card_issue_date("ValidFrom: " + issuedate);
}

void set_emv_expire_date(String expiredate) {
nfc_emv_read_page->set_card_expire_date("ValidTo: " + expiredate);
}

static bool nfc_initialized = false;

void init_nfc_navigation(Gui *_gui) {
Expand Down
27 changes: 22 additions & 5 deletions lib/UI/navigation/NFC/NFCNavigation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,23 @@

#include "gui.hpp"

void init_nfc_navigation(Gui *_gui);
void goto_nfc_gui();

void stop_nfc_polling();
void save_dump_to_sd();
void nfc_mifare_polling();
void nfc_felica_polling();
void format_nfc_tag();
void goto_home();
void nfc_return_back();
void open_nfc_dump_browser();
void bruteforce_a_tag();
void init_nfc_felica_polling_result_gui(uint8_t *idm, uint8_t *pmm,
uint16_t sys_code);


// Emulator
void emulate_iso14443a();
void emulate_iso18092();

// GUI function
void goto_nfc_gui();
void set_dumped_sectors(int sectors);
void set_unreadable_sectors(int sectors);
void set_unauthenticated_sectors(int sectors);
Expand All @@ -47,7 +49,22 @@ void nfc_bruteforce_set_tried_key(uint8_t attemps);
void goto_nfc_dump_result_gui();
void goto_nfc_polling_result_gui(uint8_t *uid, uint8_t len,
const char *tag_name);
void goto_home();
void nfc_return_back();
void set_emv_type(String type);
void set_emv_pan(String pan);
void set_emv_issue_date(String issuedate);
void set_emv_expire_date(String expiredate);

// FeliCa feature
void felica_dump();
void nfc_felica_polling();

// EMV feature
void read_emv_card();

// Misc
void init_nfc_navigation(Gui *_gui);
void nfc_cleanup();
String get_current_pn532_version();
#endif
45 changes: 45 additions & 0 deletions lib/UI/pages/NFC/NFCEMVReadPage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* This file is part of the Capibara zero (https://github.com/CapibaraZero/fw or
* https://capibarazero.github.io/). Copyright (c) 2025 Andrea Canale.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "NFCEMVReadPage.hpp"
#include "../../navigation/NFC/NFCNavigation.hpp"

#include "gui.hpp"

NFCEMVReadPage::~NFCEMVReadPage() {
delete card_type;
delete card_number;
delete issue_date;
delete expire;
}

void NFCEMVReadPage::display() {
grid = new Grid(screen, 5, 1);
card_type = new Text(screen, ST77XX_WHITE, "Scanning EMV...");
card_number = new Text(screen, ST77XX_WHITE, "");
issue_date = new Text(screen, ST77XX_WHITE, "");
expire = new Text(screen, ST77XX_WHITE, "");
exit_page = new List(screen, "Exit", 2, ST77XX_WHITE, 20, ST77XX_BLUE, goto_home);
grid->add(card_type);
grid->add(card_number);
grid->add(issue_date);
grid->add(expire);
grid->add(exit_page);
grid->set_y_spacing(20);
grid->set_selected(4, true);
grid->display();
}
60 changes: 60 additions & 0 deletions lib/UI/pages/NFC/NFCEMVReadPage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This file is part of the Capibara zero (https://github.com/CapibaraZero/fw or
* https://capibarazero.github.io/). Copyright (c) 2025 Andrea Canale.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "../../i18n.hpp"
#include "../../i18n/NFC/nfc_format_page_keys.h"
#include "../Page.hpp"
#include "Grid.hpp"
#include "List.hpp"
#include "Text.hpp"

#ifndef NFC_EMV_READ_PAGE_H
#define NFC_EMV_READ_PAGE_H

class NFCEMVReadPage : public Page {
private:
Text *card_type;
Text *card_number;
Text *issue_date;
Text *expire;
List *exit_page;

public:
NFCEMVReadPage(uint8_t _position_limit, uint8_t _lower_limit,
uint8_t _position_increment, GFXForms *screen)
: Page(_position_limit, _lower_limit, _position_increment, screen) {
};
~NFCEMVReadPage();
void display();

void click(int pos, void callback()) { grid->click(pos, callback); };
void set_selected(int pos, bool status) { grid->set_selected(pos, status); };
void set_card_type(String type) {
card_type->set_text(type);
}
void set_card_pan(String pan) {
card_number->set_text(pan);
}
void set_card_issue_date(String issuedate) {
issue_date->set_text(issuedate);
}
void set_card_expire_date(String expiredate) {
expire->set_text(expiredate);
}
};

#endif
6 changes: 5 additions & 1 deletion lib/UI/pages/NFC/NFCMainPage.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of the Capibara zero (https://github.com/CapibaraZero/fw or
* https://capibarazero.github.io/). Copyright (c) 2024 Andrea Canale.
* https://capibarazero.github.io/). Copyright (c) 2025 Andrea Canale.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -25,6 +25,7 @@
NFCMainPage::~NFCMainPage() {
delete polling_iso14443_a;
delete polling_felica;
delete read_emv;
delete go_back;
}

Expand All @@ -35,10 +36,13 @@ void NFCMainPage::display() {
ST77XX_WHITE, 20, ST77XX_BLACK, nfc_mifare_polling);
polling_felica = new List(screen, english_words->at(POLLING_ISO18092_KEY), 2,
ST77XX_WHITE, 20, ST77XX_BLACK, nfc_felica_polling);
read_emv = new List(screen, "Read EMV", 2,
ST77XX_WHITE, 20, ST77XX_BLACK, read_emv_card);
go_back = new List(screen, english_words->at(NFC_POLLING_GO_BACK_KEY), 2,
ST77XX_WHITE, 20, ST77XX_BLACK, goto_home);
grid->add(polling_iso14443_a);
grid->add(polling_felica);
grid->add(read_emv);
grid->add(go_back);
grid->set_selected(0, true);
grid->display();
Expand Down
3 changes: 2 additions & 1 deletion lib/UI/pages/NFC/NFCMainPage.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of the Capibara zero (https://github.com/CapibaraZero/fw or
* https://capibarazero.github.io/). Copyright (c) 2024 Andrea Canale.
* https://capibarazero.github.io/). Copyright (c) 2025 Andrea Canale.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -26,6 +26,7 @@ class NFCMainPage : public Page {
private:
List *polling_iso14443_a;
List *polling_felica;
List *read_emv;
List *go_back;

public:
Expand Down
102 changes: 101 additions & 1 deletion lib/nfc_attacks/nfc_attacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include "fm.hpp"
#include "helper.hpp"
#include "posixsd.hpp"
#include "BerTlv.h"
#include <bits/stdc++.h>

using namespace std;

Expand Down Expand Up @@ -570,4 +572,102 @@ bool NFCAttacks::emulate_tag(uint8_t *uid) {

bool NFCAttacks::emulate_tag(uint8_t *idm, uint8_t *pmm, uint8_t *sys_code) {
return nfc_framework.emulate_tag(idm, pmm, sys_code);
};
};

void NFCAttacks::parse_pan(std::vector<uint8_t> *afl_content, EMVCard *card) {
auto pos = find(afl_content->begin(), afl_content->end(), 0x5A);
uint8_t len = *(pos+1);
uint8_t pan_begin = distance(afl_content->begin(), pos) + 2;
card->pan = (uint8_t *)malloc(len);
memcpy(card->pan, &afl_content->data()[pan_begin], len);
card->pan_len = len;
}

void NFCAttacks::parse_validfrom(std::vector<uint8_t> *afl_content, EMVCard *card) {
bool found = false;
for (size_t i = 0; i < afl_content->size() && !found; i++) {
if(afl_content->at(i) == 0x5F && afl_content->at(i+1) == 0x25) {
size_t begin = i+3; // The format is 0x5F 0x25 SIZE DATA so skip 3 bytes
card->validfrom = (uint8_t *) malloc(2);
// The format in card is YEAR/MONTH but I want MONTH/YEAR since is the standard format
card->validfrom[0] = afl_content->at(begin+1);
card->validfrom[1] = afl_content->at(begin);
found = true;
}
}
}

void NFCAttacks::parse_validto(std::vector<uint8_t> *afl_content, EMVCard *card) {
bool found = false;
for (size_t i = 0; i < afl_content->size() && !found; i++) {
if(afl_content->at(i) == 0x5F && afl_content->at(i+1) == 0x24) {
size_t begin = i+3; // The format is 0x5F 0x24 SIZE DATA so skip 3 bytes
card->validto = (uint8_t *) malloc(2);
// The format in card is YEAR/MONTH but I want MONTH/YEAR since is the standard format
card->validto[0] = afl_content->at(begin+1);
card->validto[1] = afl_content->at(begin);
found = true;
}
}
}

void NFCAttacks::get_afl(EMVCard *card, uint8_t *afl) {
uint8_t P2 = (afl[0] >> 3) <<3 | 0b00000100; // Calculate P2 from afl
std::vector<uint8_t> afl_content = nfc_framework.emv_read_afl(P2);
if(!afl_content.empty()) {
BerTlv Tlv;
Tlv.SetTlv(afl_content);
std::vector<uint8_t> container;
if(Tlv.GetValue("5A", &container) != OK) { // Get PAN(Credit Card Number). If TLV is corrupted(happens often with PN532 fallback to a workaround to find information)
parse_pan(&afl_content, card);
parse_validfrom(&afl_content, card);
parse_validto(&afl_content, card);
} else {
memcpy(card->pan, container.data(), container.size()); // Copy data from TLV to struct
container.clear();
if(Tlv.GetValue("5F25", &container) != OK) { // Get ValidFrom date
parse_validfrom(&afl_content, card);
parse_validto(&afl_content, card);
} else {
memcpy(card->validfrom, container.data(), container.size());
if(Tlv.GetValue("5F24", &container) != OK) { // Get ValidTo date
parse_validto(&afl_content, card);
} else {
memcpy(card->validto, container.data(), container.size());
}
}
}
} else {
Serial.println("Can't parse AFL data");
}
}

EMVCard NFCAttacks::read_emv_card() {
EMVCard res;
std::vector<uint8_t> aid = nfc_framework.emv_ask_for_aid(); // Perform Application Selection
if(aid.empty()) { // If we can't get AID, we can't read the card
res.parsed = false;
Serial.println("Can't read card");
} else {
// Copy AID to result card
res.aid = (uint8_t *)malloc(aid.size());
memcpy(res.aid, aid.data(), aid.size());

// Initialize Application Process
std::vector<uint8_t> pdol = nfc_framework.emv_ask_for_pdol(&aid); // Check if card require PDOL(for example, VISA)

if(pdol.empty()) { // No PDOL(for example Mastercard)
std::vector<uint8_t> afl = nfc_framework.emv_ask_for_afl(); // Try to get AFL without PDOL

if(!afl.empty()) {
// Read Application data
get_afl(&res, afl.data()); // Read AFL
} else {
Serial.println("Can't get AFL ID");
}
} else {
// TODO: Implement PDOL reading
}
}
return res;
}
Loading

0 comments on commit 0f8c2f0

Please sign in to comment.