From 3c5fce2ba7d49f3ebea05eed187a9b5ee8189803 Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko Date: Wed, 22 Nov 2017 07:16:33 +0200 Subject: [PATCH] Add: Emv first part of commands * hf emv search * hf emv pse * hf emv select * hf emv exec command - only part of functionality --- CHANGELOG.md | 4 + client/Makefile | 2 + client/cmdhf.c | 2 + client/cmdhf14a.c | 10 +- client/cmdhf14a.h | 2 +- client/emv/cmdemv.c | 552 ++++++++++++++++++++++++++++++++++++++++++ client/emv/cmdemv.h | 31 +++ client/emv/dol.c | 135 +++++++++++ client/emv/dol.h | 25 ++ client/emv/dump.c | 2 +- client/emv/dump.h | 1 + client/emv/emv_tags.c | 78 +++++- client/emv/emvcore.c | 432 ++++++++++++++++++++++++++++++++- client/emv/emvcore.h | 24 +- client/emv/tlv.c | 44 +++- client/emv/tlv.h | 5 + client/util.c | 37 ++- client/util.h | 3 + 18 files changed, 1370 insertions(+), 19 deletions(-) create mode 100644 client/emv/cmdemv.c create mode 100644 client/emv/cmdemv.h create mode 100644 client/emv/dol.c create mode 100644 client/emv/dol.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e6b827..fabd04b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed proxmark command line parameter `flush` to `-f` or `-flush` (Merlok) - Changed `hf 14a reader` to just reqest-anticilission-select sequence (Merlok) - Changed `hf 14a raw` - works with LED's and some exchange logic (Merlok) +- Changed TLV parser messages to more convenient (Merlok) ### Fixed - Changed start sequence in Qt mode (fix: short commands hangs main Qt thread) (Merlok) @@ -38,6 +39,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added to `hf 14a info` detection of weak prng from Iceman1001 fork (Merlok) - Added to `hf 14a apdu` - exchange apdu via iso1443-4 (Merlok) - Added to `hf 14a apdu` - apdu and tlv results parser (Merlok) +- Added `hf emv` group of commands (Merlok) +- Added `hf emv search` `hf emv pse` - commands for selection of EMV application (Merlok) +- Added `hf emv select` - command for select EMV application (Merlok) ## [3.0.1][2017-06-08] diff --git a/client/Makefile b/client/Makefile index 30e91efb..5df99de7 100644 --- a/client/Makefile +++ b/client/Makefile @@ -111,7 +111,9 @@ CMDSRCS = crapto1/crapto1.c\ emv/dump.c\ emv/tlv.c\ emv/emv_tags.c\ + emv/dol.c\ emv/emvcore.c\ + emv/cmdemv.c\ cmdhf.c \ cmdhf14a.c \ cmdhf14b.c \ diff --git a/client/cmdhf.c b/client/cmdhf.c index 453635b7..7a2f3252 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -30,6 +30,7 @@ #include "cmdhfmfu.h" #include "cmdhftopaz.h" #include "protocols.h" +#include "emv/cmdemv.h" static int CmdHelp(const char *Cmd); @@ -703,6 +704,7 @@ static command_t CommandTable[] = {"14b", CmdHF14B, 1, "{ ISO14443B RFIDs... }"}, {"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"}, {"epa", CmdHFEPA, 1, "{ German Identification Card... }"}, + {"emv", CmdHFEMV, 1, "{ EMV cards... }"}, {"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"}, {"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"}, {"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"}, diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index 50071464..a0f3ce57 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -648,7 +648,7 @@ void DropField() { SendCommand(&c); } -int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int *dataoutlen) { +int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { uint16_t cmdc = 0; if (activateField) { @@ -686,6 +686,12 @@ int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool lea *dataoutlen = iLen - 2; if (*dataoutlen < 0) *dataoutlen = 0; + + if (maxdataoutlen && *dataoutlen > maxdataoutlen) { + PrintAndLog("APDU ERROR: Buffer too small(%d). Needs %d bytes", *dataoutlen, maxdataoutlen); + return 2; + } + memcpy(dataout, recv, *dataoutlen); if(!iLen) { @@ -779,7 +785,7 @@ int CmdHF14AAPDU(const char *cmd) { PrintAndLog(">>>>[%s%s%s] %s", activateField ? "sel ": "", leaveSignalON ? "keep ": "", decodeTLV ? "TLV": "", sprint_hex(data, datalen)); - int res = ExchangeAPDU14a(data, datalen, activateField, leaveSignalON, data, &datalen); + int res = ExchangeAPDU14a(data, datalen, activateField, leaveSignalON, data, USB_CMD_DATA_SIZE, &datalen); if (res) return res; diff --git a/client/cmdhf14a.h b/client/cmdhf14a.h index 6f6df907..401cead0 100644 --- a/client/cmdhf14a.h +++ b/client/cmdhf14a.h @@ -31,6 +31,6 @@ int CmdHF14ASnoop(const char *Cmd); char* getTagInfo(uint8_t uid); extern void DropField(); -extern int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int *dataoutlen); +extern int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); #endif diff --git a/client/emv/cmdemv.c b/client/emv/cmdemv.c new file mode 100644 index 00000000..3c3c1f11 --- /dev/null +++ b/client/emv/cmdemv.c @@ -0,0 +1,552 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// EMV commands +//----------------------------------------------------------------------------- + +#include "cmdemv.h" + +int UsageCmdHFEMVSelect(void) { + PrintAndLog("HELP : Executes select applet command:\n"); + PrintAndLog("Usage: hf emv select [-s][-k][-a][-t] \n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv select -s a00000000101 -> select card, select applet"); + PrintAndLog(" hf emv select -s -t a00000000101 -> select card, select applet, show result in TLV"); + return 0; +} + +int CmdHFEMVSelect(const char *cmd) { + uint8_t data[APDU_AID_LEN] = {0}; + int datalen = 0; + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVSelect(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVSelect(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + + if (isxdigit(c)) { + switch(param_gethex_to_eol(cmd, cmdp, data, sizeof(data), &datalen)) { + case 1: + PrintAndLog("Invalid HEX value."); + return 1; + case 2: + PrintAndLog("AID too large."); + return 1; + case 3: + PrintAndLog("Hex must have even number of digits."); + return 1; + } + + // we get all the hex to end of line with spaces + break; + } + + + cmdp++; + } + + // exec + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = EMVSelect(activateField, leaveSignalON, data, datalen, buf, sizeof(buf), &len, &sw, NULL); + + if (sw) + PrintAndLog("APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + + if (res) + return res; + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + + return 0; +} + +int UsageCmdHFEMVSearch(void) { + PrintAndLog("HELP : Tries to select all applets from applet list:\n"); + PrintAndLog("Usage: hf emv search [-s][-k][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results of selected applets\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv search -s -> select card and search"); + PrintAndLog(" hf emv search -s -t -> select card, search and show result in TLV"); + return 0; +} + +int CmdHFEMVSearch(const char *cmd) { + + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVSearch(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVSearch(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + struct tlvdb *t = NULL; + const char *al = "Applets list"; + t = tlvdb_fixed(1, strlen(al), (const unsigned char *)al); + + if (EMVSearch(activateField, leaveSignalON, decodeTLV, t)) { + tlvdb_free(t); + return 2; + } + + PrintAndLog("Search completed."); + + // print list here + if (!decodeTLV) { + TLVPrintAIDlistFromSelectTLV(t); + } + + tlvdb_free(t); + + return 0; +} + +int UsageCmdHFEMVPPSE(void) { + PrintAndLog("HELP : Executes PSE/PPSE select command. It returns list of applet on the card:\n"); + PrintAndLog("Usage: hf emv pse [-s][-k][-1][-2][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -1 : ppse (1PAY.SYS.DDF01)"); + PrintAndLog(" -2 : pse (2PAY.SYS.DDF01)"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv pse -s -1 -> select, get pse"); + PrintAndLog(" hf emv pse -s -k -2 -> select, get ppse, keep field"); + PrintAndLog(" hf emv pse -s -t -2 -> select, get ppse, show result in TLV"); + return 0; +} + +int CmdHFEMVPPSE(const char *cmd) { + + uint8_t PSENum = 2; + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVPPSE(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVPPSE(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + case '1': + PSENum = 1; + break; + case '2': + PSENum = 2; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + // exec + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = EMVSelectPSE(activateField, leaveSignalON, PSENum, buf, sizeof(buf), &len, &sw); + + if (sw) + PrintAndLog("APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + + if (res) + return res; + + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + + return 0; +} + +int UsageCmdHFEMVExec(void) { + PrintAndLog("HELP : Executes EMV contactless transaction:\n"); + PrintAndLog("Usage: hf emv exec [-s][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog(" -f : force search AID. Search AID instead of execute PPSE.\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv pse -s -> select card"); + PrintAndLog(" hf emv pse -s -t -a -> select card, show responses in TLV, show APDU"); + return 0; +} + +#define TLV_ADD(tag, value)( tlvdb_add(tlvRoot, tlvdb_fixed(tag, sizeof(value) - 1, (const unsigned char *)value)) ) + +int CmdHFEMVExec(const char *cmd) { + bool activateField = false; + bool showAPDU = false; + bool decodeTLV = false; + bool forceSearch = false; + + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t AID[APDU_AID_LEN] = {0}; + size_t AIDlen = 0; + + int res; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVExec(); + return 0; + } + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVPPSE(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'a': + case 'A': + showAPDU = true; + break; + case 't': + case 'T': + decodeTLV = true; + break; + case 'f': + case 'F': + forceSearch = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + + // init applets list tree + struct tlvdb *tlvSelect = NULL; + const char *al = "Applets list"; + tlvSelect = tlvdb_fixed(1, strlen(al), (const unsigned char *)al); + + // Application Selection + // https://www.openscdp.org/scripts/tutorial/emv/applicationselection.html + if (!forceSearch) { + // PPSE + PrintAndLog("\n* PPSE."); + SetAPDULogging(showAPDU); + res = EMVSearchPSE(activateField, true, decodeTLV, tlvSelect); + + // check PPSE and select application id + if (!res) { + TLVPrintAIDlistFromSelectTLV(tlvSelect); + EMVSelectApplication(tlvSelect, AID, &AIDlen); + } + } + + // Search + if (!AIDlen) { + PrintAndLog("\n* Search AID in list."); + SetAPDULogging(false); + if (EMVSearch(activateField, true, decodeTLV, tlvSelect)) { + tlvdb_free(tlvSelect); + return 2; + } + + // check search and select application id + TLVPrintAIDlistFromSelectTLV(tlvSelect); + EMVSelectApplication(tlvSelect, AID, &AIDlen); + } + + // Init TLV tree + struct tlvdb *tlvRoot = NULL; + const char *alr = "Root terminal TLV tree"; + tlvRoot = tlvdb_fixed(1, strlen(alr), (const unsigned char *)alr); + + // check if we found EMV application on card + if (!AIDlen) { + PrintAndLog("Can't select AID. EMV AID not found"); + return 2; + } + + // Select + PrintAndLog("\n* Selecting AID:%s", sprint_hex_inrow(AID, AIDlen)); + SetAPDULogging(showAPDU); + res = EMVSelect(false, true, AID, AIDlen, buf, sizeof(buf), &len, &sw, tlvRoot); + + if (res) { + PrintAndLog("Can't select AID (%d). Exit...", res); + return 3; + } + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + PrintAndLog("* Selected."); + +PrintAndLog("-----BREAK."); +return 0; + PrintAndLog("\n* Init transaction parameters."); + + //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4 + TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // E6 + //9F02:(Amount, Authorised (Numeric)) len:6 + TLV_ADD(0x9F02, "\x00\x00\x00\x00\x01\x00"); + //9F1A:(Terminal Country Code) len:2 + TLV_ADD(0x9F1A, "ru"); + //5F2A:(Transaction Currency Code) len:2 + // USD 840, EUR 978, RUR 810, RUB 643, RUR 810(old), UAH 980, AZN 031, n/a 999 + TLV_ADD(0x5F2A, "\x09\x80"); + //9A:(Transaction Date) len:3 + TLV_ADD(0x9A, "\x00\x00\x00"); + //9C:(Transaction Type) len:1 | 00 => Goods and service #01 => Cash + TLV_ADD(0x9C, "\x00"); + // 9F37 Unpredictable Number len:4 + TLV_ADD(0x9F37, "\x01\x02\x03\x04"); + + TLVPrintFromTLV(tlvRoot); + + PrintAndLog("\n* Calc PDOL."); + struct tlv *pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83); + if (!pdol_data_tlv){ + PrintAndLog("ERROR: can't create PDOL TLV."); + return 4; + } + + size_t pdol_data_tlv_data_len; + unsigned char *pdol_data_tlv_data = tlv_encode(pdol_data_tlv, &pdol_data_tlv_data_len); + if (!pdol_data_tlv_data) { + PrintAndLog("ERROR: can't create PDOL data."); + return 4; + } + PrintAndLog("PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len)); + +//PrintAndLog("-----BREAK."); +//return 0; + PrintAndLog("\n* GPO."); + res = EMVGPO(true, pdol_data_tlv_data, pdol_data_tlv_data_len, buf, sizeof(buf), &len, &sw, tlvRoot); + + free(pdol_data_tlv); + + if (res) { + PrintAndLog("GPO error(%d): %4x. Exit...", res, sw); + return 5; + } + + // process response template format 1 [id:80 2b AIP + x4b AFL] and format 2 [id:77 TLV] + if (buf[0] == 0x80) { + + + + if (decodeTLV){ + PrintAndLog("GPO response format1:"); + TLVPrintFromBuffer(buf, len); + } + } else { + + + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + } + + PrintAndLog("\n* Read records from AFL."); + const struct tlv *AFL = tlvdb_get(tlvRoot, 0x94, NULL); + if (!AFL || !AFL->len) { + PrintAndLog("WARNING: AFL not found."); + } + + while(AFL && AFL->len) { + if (AFL->len % 4) { + PrintAndLog("ERROR: Wrong AFL length: %d", AFL->len); + break; + } + + for (int i = 0; i < AFL->len / 4; i++) { + uint8_t SFI = AFL->value[i * 4 + 0] >> 3; + uint8_t SFIstart = AFL->value[i * 4 + 1]; + uint8_t SFIend = AFL->value[i * 4 + 2]; + uint8_t SFIoffline = AFL->value[i * 4 + 3]; + + PrintAndLog("* * SFI[%02x] start:%02x end:%02x offline:%02x", SFI, SFIstart, SFIend, SFIoffline); + if (SFI == 0 || SFI == 31 || SFIstart == 0 || SFIstart > SFIend) { + PrintAndLog("SFI ERROR! Skipped..."); + continue; + } + + for(int n = SFIstart; n <= SFIend; n++) { + PrintAndLog("* * * SFI[%02x] %d", SFI, n); + + res = EMVReadRecord(true, SFI, n, buf, sizeof(buf), &len, &sw, tlvRoot); + if (res) { + PrintAndLog("ERROR SFI[%02x]. APDU error %4x", SFI, sw); + continue; + } + + if (decodeTLV) { + TLVPrintFromBuffer(buf, len); + PrintAndLog(""); + } + + if (SFIoffline) { + // here will be offline records storing... + // dont foget: if (sfi < 11) + } + } + } + + break; + } + + // additional contacless EMV commands (fDDA, CDA, external authenticate) + + + // DropField + DropField(); + + // Destroy TLV's + tlvdb_free(tlvSelect); + tlvdb_free(tlvRoot); + + PrintAndLog("\n* Transaction completed."); + + return 0; +} + +int CmdHelp(const char *Cmd); +static command_t CommandTable[] = { + {"help", CmdHelp, 1, "This help"}, + {"exec", CmdHFEMVExec, 0, "Executes EMV contactless transaction."}, + {"pse", CmdHFEMVPPSE, 0, "Execute PPSE. It selects 2PAY.SYS.DDF01 or 1PAY.SYS.DDF01 directory."}, + {"search", CmdHFEMVSearch, 0, "Try to select all applets from applets list and print installed applets."}, + {"select", CmdHFEMVSelect, 0, "Select applet."}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFEMV(const char *Cmd) { + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +} diff --git a/client/emv/cmdemv.h b/client/emv/cmdemv.h new file mode 100644 index 00000000..78796efa --- /dev/null +++ b/client/emv/cmdemv.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// EMV commands +//----------------------------------------------------------------------------- + +#ifndef CMDEMV_H__ +#define CMDEMV_H__ + +#include +#include +#include +#include +#include "proxmark3.h" +#include "ui.h" +#include "cmdparser.h" +#include "common.h" +#include "util.h" +#include "util_posix.h" +#include "cmdmain.h" +#include "emvcore.h" +#include "apduinfo.h" + +int CmdHFEMV(const char *Cmd); + + +#endif \ No newline at end of file diff --git a/client/emv/dol.c b/client/emv/dol.c new file mode 100644 index 00000000..0fc1c49b --- /dev/null +++ b/client/emv/dol.c @@ -0,0 +1,135 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "emv/dol.h" +#include "emv/tlv.h" + +#include +#include + +static size_t dol_calculate_len(const struct tlv *tlv, size_t data_len) +{ + if (!tlv) + return 0; + + const unsigned char *buf = tlv->value; + size_t left = tlv->len; + size_t count = 0; + + while (left) { + struct tlv tlv; + if (!tlv_parse_tl(&buf, &left, &tlv)) + return 0; + + count += tlv.len; + + /* Last tag can be of variable length */ + if (tlv.len == 0 && left == 0) + count = data_len; + } + + return count; +} + +struct tlv *dol_process(const struct tlv *tlv, const struct tlvdb *tlvdb, tlv_tag_t tag) +{ + size_t res_len; + if (!tlv || !(res_len = dol_calculate_len(tlv, 0))) { + struct tlv *res_tlv = malloc(sizeof(*res_tlv)); + + res_tlv->tag = tag; + res_tlv->len = 0; + res_tlv->value = NULL; + + return res_tlv; + } + + struct tlv *res_tlv = malloc(sizeof(*res_tlv) + res_len); + if (!res_tlv) + return NULL; + + const unsigned char *buf = tlv->value; + size_t left = tlv->len; + unsigned char *res = (unsigned char *)(res_tlv + 1); + size_t pos = 0; + + while (left) { + struct tlv cur_tlv; + if (!tlv_parse_tl(&buf, &left, &cur_tlv) || pos + cur_tlv.len > res_len) { + free(res_tlv); + + return NULL; + } + + const struct tlv *tag_tlv = tlvdb_get(tlvdb, cur_tlv.tag, NULL); + if (!tag_tlv) { + memset(res + pos, 0, cur_tlv.len); + } else if (tag_tlv->len > cur_tlv.len) { + memcpy(res + pos, tag_tlv->value, cur_tlv.len); + } else { + // FIXME: cn data should be padded with 0xFF !!! + memcpy(res + pos, tag_tlv->value, tag_tlv->len); + memset(res + pos + tag_tlv->len, 0, cur_tlv.len - tag_tlv->len); + } + pos += cur_tlv.len; + } + + res_tlv->tag = tag; + res_tlv->len = res_len; + res_tlv->value = res; + + return res_tlv; +} + +struct tlvdb *dol_parse(const struct tlv *tlv, const unsigned char *data, size_t data_len) +{ + if (!tlv) + return NULL; + + const unsigned char *buf = tlv->value; + size_t left = tlv->len; + size_t res_len = dol_calculate_len(tlv, data_len); + size_t pos = 0; + struct tlvdb *db = NULL; + + if (res_len != data_len) + return NULL; + + while (left) { + struct tlv cur_tlv; + if (!tlv_parse_tl(&buf, &left, &cur_tlv) || pos + cur_tlv.len > res_len) { + tlvdb_free(db); + return NULL; + } + + /* Last tag can be of variable length */ + if (cur_tlv.len == 0 && left == 0) + cur_tlv.len = res_len - pos; + + struct tlvdb *tag_db = tlvdb_fixed(cur_tlv.tag, cur_tlv.len, data + pos); + if (!db) + db = tag_db; + else + tlvdb_add(db, tag_db); + + pos += cur_tlv.len; + } + + return db; +} diff --git a/client/emv/dol.h b/client/emv/dol.h new file mode 100644 index 00000000..9b1b5165 --- /dev/null +++ b/client/emv/dol.h @@ -0,0 +1,25 @@ +/* + * libopenemv - a library to work with EMV family of smart cards + * Copyright (C) 2015 Dmitry Eremin-Solenikov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#ifndef DOL_H +#define DOL_H + +#include "emv/tlv.h" +#include + +struct tlv *dol_process(const struct tlv *tlv, const struct tlvdb *tlvdb, tlv_tag_t tag); +struct tlvdb *dol_parse(const struct tlv *tlv, const unsigned char *buf, size_t len); + +#endif diff --git a/client/emv/dump.c b/client/emv/dump.c index 41d7c9fd..2368680a 100644 --- a/client/emv/dump.c +++ b/client/emv/dump.c @@ -18,8 +18,8 @@ #endif #include "dump.h" - #include +#include #define PRINT_INDENT(level) {for (int i = 0; i < (level); i++) fprintf(f, "\t");} diff --git a/client/emv/dump.h b/client/emv/dump.h index 567a134f..a981615a 100644 --- a/client/emv/dump.h +++ b/client/emv/dump.h @@ -21,5 +21,6 @@ void dump_buffer_simple(const unsigned char *ptr, size_t len, FILE *f); void dump_buffer(const unsigned char *ptr, size_t len, FILE *f, int level); +void dump_buffer_tab(const unsigned char *ptr, size_t len, FILE *f, int tabs); #endif diff --git a/client/emv/emv_tags.c b/client/emv/emv_tags.c index d91685c4..a7c11c3e 100644 --- a/client/emv/emv_tags.c +++ b/client/emv/emv_tags.c @@ -29,6 +29,7 @@ enum emv_tag_t { EMV_TAG_BITMASK, EMV_TAG_DOL, EMV_TAG_CVM_LIST, + EMV_TAG_AFL, EMV_TAG_STRING, EMV_TAG_NUMERIC, EMV_TAG_YYMMDD, @@ -57,7 +58,7 @@ static const struct emv_tag_bit EMV_AIP[] = { { EMV_BIT(1, 3), "Issuer authentication is supported" }, { EMV_BIT(1, 2), "Reserved for use by the EMV Contactless Specifications" }, { EMV_BIT(1, 1), "CDA supported" }, - { EMV_BIT(2, 8), "Reserved for use by the EMV Contactless Specifications" }, + { EMV_BIT(2, 8), "MSD is supported (Magnetic Stripe Data)" }, { EMV_BIT(2, 7), "Reserved for use by the EMV Contactless Specifications" }, { EMV_BIT(2, 6), "Reserved for use by the EMV Contactless Specifications" }, { EMV_BIT(2, 1), "Reserved for use by the EMV Contactless Specifications" }, @@ -113,8 +114,44 @@ static const struct emv_tag_bit EMV_TVR[] = { EMV_BIT_FINISH, }; +static const struct emv_tag_bit EMV_CTQ[] = { + { EMV_BIT(1, 8), "Online PIN Required" }, + { EMV_BIT(1, 7), "Signature Required" }, + { EMV_BIT(1, 6), "Go Online if Offline Data Authentication Fails and Reader is online capable" }, + { EMV_BIT(1, 5), "Switch Interface if Offline Data Authentication fails and Reader supports VIS" }, + { EMV_BIT(1, 4), "Go Online if Application Expired" }, + { EMV_BIT(1, 3), "Switch Interface for Cash Transactions" }, + { EMV_BIT(1, 2), "Switch Interface for Cashback Transactions" }, + { EMV_BIT(2, 8), "Consumer Device CVM Performed" }, + { EMV_BIT(2, 7), "Card supports Issuer Update Processing at the POS" }, + EMV_BIT_FINISH, +}; + +static const struct emv_tag_bit EMV_TTQ[] = { + { EMV_BIT(1, 8), "MSD supported" }, + { EMV_BIT(1, 7), "VSDC supported" }, + { EMV_BIT(1, 6), "qVSDC supported" }, + { EMV_BIT(1, 5), "EMV contact chip supported" }, + { EMV_BIT(1, 4), "Offline-only reader" }, + { EMV_BIT(1, 3), "Online PIN supported" }, + { EMV_BIT(1, 2), "Signature supported" }, + { EMV_BIT(1, 1), "Offline Data Authentication (ODA) for Online Authorizations supported\nWarning!!!! Readers compliant to this specification set TTQ byte 1 bit 1 (this field) to 0b" }, + { EMV_BIT(2, 8), "Online cryptogram required" }, + { EMV_BIT(2, 7), "CVM required" }, + { EMV_BIT(2, 6), "(Contact Chip) Offline PIN supported" }, + { EMV_BIT(3, 8), "Issuer Update Processing supported" }, + { EMV_BIT(3, 7), "Mobile functionality supported (Consumer Device CVM)" }, + EMV_BIT_FINISH, +}; + +// All Data Elements by Tags used in TLV structure (according to the EMV 4.2 Standard ) +// https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags +// http://dexterous-programmer.blogspot.in/2012/05/emv-tags.html static const struct emv_tag emv_tags[] = { { 0x00 , "Unknown ???" }, + { 0x01 , "", EMV_TAG_STRING }, // string for headers + { 0x41 , "Country code and national data" }, + { 0x42 , "Issuer Identification Number (IIN)" }, { 0x4f , "Application Dedicated File (ADF) Name" }, { 0x50 , "Application Label", EMV_TAG_STRING }, { 0x56 , "Track 1 Data" }, @@ -147,12 +184,13 @@ static const struct emv_tag emv_tags[] = { { 0x91 , "Issuer Authentication Data" }, { 0x92 , "Issuer Public Key Remainder" }, { 0x93 , "Signed Static Application Data" }, - { 0x94 , "Application File Locator (AFL)" }, + { 0x94 , "Application File Locator (AFL)", EMV_TAG_AFL }, { 0x95 , "Terminal Verification Results" }, { 0x9a , "Transaction Date", EMV_TAG_YYMMDD }, { 0x9c , "Transaction Type" }, { 0x9f02, "Amount, Authorised (Numeric)", EMV_TAG_NUMERIC }, { 0x9f03, "Amount, Other (Numeric)", EMV_TAG_NUMERIC, }, + { 0x9f06, "Application Identifier (AID), Terminal. ISO 7816-5" }, { 0x9f07, "Application Usage Control", EMV_TAG_BITMASK, &EMV_AUC }, { 0x9f08, "Application Version Number" }, { 0x9f0d, "Issuer Action Code - Default", EMV_TAG_BITMASK, &EMV_TVR }, @@ -168,6 +206,7 @@ static const struct emv_tag emv_tags[] = { { 0x9f21, "Transaction Time" }, { 0x9f26, "Application Cryptogram" }, { 0x9f27, "Cryptogram Information Data" }, + { 0x9f2a, "Kernel Identifier" }, { 0x9f2d, "ICC PIN Encipherment Public Key Certificate" }, { 0x9f2e, "ICC PIN Encipherment Public Key Exponent" }, { 0x9f2f, "ICC PIN Encipherment Public Key Remainder" }, @@ -193,9 +232,12 @@ static const struct emv_tag emv_tags[] = { { 0x9f63, "PUNATC(Track1)" }, { 0x9f64, "NATC(Track1)" }, { 0x9f65, "PCVC3(Track2)" }, - { 0x9f66, "PUNATC(Track2)" }, - { 0x9f67, "NATC(Track2)" }, + { 0x9f66, "PUNATC(Track2) / Terminal Transaction Qualifiers (TTQ)", EMV_TAG_BITMASK, &EMV_TTQ }, + { 0x9f67, "NATC(Track2) / MSD Offset" }, + { 0x9f69, "Card Authentication Related Data" }, + { 0x9f6a, "Unpredictable Number", EMV_TAG_NUMERIC }, { 0x9f6b, "Track 2 Data" }, + { 0x9f6c, "Card Transaction Qualifiers (CTQ)", EMV_TAG_BITMASK, &EMV_CTQ }, { 0xa5 , "File Control Information (FCI) Proprietary Template" }, { 0xbf0c, "File Control Information (FCI) Issuer Discretionary Data" }, }; @@ -242,10 +284,11 @@ static void emv_tag_dump_bitmask(const struct tlv *tlv, const struct emv_tag *ta PRINT_INDENT(level); fprintf(f, "\tByte %u (%02x)\n", byte, val); for (bit = 8; bit > 0; bit--, val <<= 1) { - if (val & 0x80) + if (val & 0x80){ PRINT_INDENT(level); fprintf(f, "\t\t%s - '%s'\n", bitstrings[bit - 1], bits->bit == EMV_BIT(byte, bit) ? bits->name : "Unknown"); + } if (bits->bit == EMV_BIT(byte, bit)) bits ++; } @@ -276,10 +319,8 @@ static void emv_tag_dump_dol(const struct tlv *tlv, const struct emv_tag *tag, F static void emv_tag_dump_string(const struct tlv *tlv, const struct emv_tag *tag, FILE *f, int level) { - PRINT_INDENT(level); fprintf(f, "\tString value '"); fwrite(tlv->value, 1, tlv->len, f); - PRINT_INDENT(level); fprintf(f, "'\n"); } @@ -433,6 +474,19 @@ static void emv_tag_dump_cvm_list(const struct tlv *tlv, const struct emv_tag *t } } +static void emv_tag_dump_afl(const struct tlv *tlv, const struct emv_tag *tag, FILE *f, int level){ + if (tlv->len < 4 || tlv->len % 4) { + PRINT_INDENT(level); + fprintf(f, "\tINVALID!\n"); + return; + } + + for (int i = 0; i < tlv->len / 4; i++) { + PRINT_INDENT(level); + fprintf(f, "SFI[%02x] start:%02x end:%02x offline:%02x\n", tlv->value[i * 4 + 0] >> 3, tlv->value[i * 4 + 1], tlv->value[i * 4 + 2], tlv->value[i * 4 + 3]); + } +} + bool emv_tag_dump(const struct tlv *tlv, FILE *f, int level) { if (!tlv) { @@ -443,20 +497,28 @@ bool emv_tag_dump(const struct tlv *tlv, FILE *f, int level) const struct emv_tag *tag = emv_get_tag(tlv); PRINT_INDENT(level); - fprintf(f, "--%2hx[%02zx] '%s':\n", tlv->tag, tlv->len, tag->name); + fprintf(f, "--%2hx[%02zx] '%s':", tlv->tag, tlv->len, tag->name); switch (tag->type) { case EMV_TAG_GENERIC: + fprintf(f, "\n"); break; case EMV_TAG_BITMASK: + fprintf(f, "\n"); emv_tag_dump_bitmask(tlv, tag, f, level); break; case EMV_TAG_DOL: + fprintf(f, "\n"); emv_tag_dump_dol(tlv, tag, f, level); break; case EMV_TAG_CVM_LIST: + fprintf(f, "\n"); emv_tag_dump_cvm_list(tlv, tag, f, level); break; + case EMV_TAG_AFL: + fprintf(f, "\n"); + emv_tag_dump_afl(tlv, tag, f, level); + break; case EMV_TAG_STRING: emv_tag_dump_string(tlv, tag, f, level); break; diff --git a/client/emv/emvcore.c b/client/emv/emvcore.c index 63a69baa..5c737b05 100644 --- a/client/emv/emvcore.c +++ b/client/emv/emvcore.c @@ -10,6 +10,77 @@ #include "emvcore.h" +// Got from here. Thanks) +// https://eftlab.co.uk/index.php/site-map/knowledge-base/211-emv-aid-rid-pix +const char *PSElist [] = { + "325041592E5359532E4444463031", // 2PAY.SYS.DDF01 - Visa Proximity Payment System Environment - PPSE + "315041592E5359532E4444463031" // 1PAY.SYS.DDF01 - Visa Payment System Environment - PSE +}; +const size_t PSElistLen = sizeof(PSElist)/sizeof(char*); + +const char *AIDlist [] = { + // Visa International + "A00000000305076010", // VISA ELO Credit + "A0000000031010", // VISA Debit/Credit (Classic) + "A0000000031010", // ddddddddddddddddddddddddddddddddddddddddddddddddddddddddd + "A000000003101001", // VISA Credit + "A000000003101002", // VISA Debit + "A0000000032010", // VISA Electron + "A0000000032020", // VISA + "A0000000033010", // VISA Interlink + "A0000000034010", // VISA Specific + "A0000000035010", // VISA Specific + "A0000000036010", // Domestic Visa Cash Stored Value + "A0000000036020", // International Visa Cash Stored Value + "A0000000038002", // VISA Auth, VisaRemAuthen EMV-CAP (DPA) + "A0000000038010", // VISA Plus + "A0000000039010", // VISA Loyalty + "A000000003999910", // VISA Proprietary ATM + // Visa USA + "A000000098", // Debit Card + "A0000000980848", // Debit Card + // Mastercard International + "A00000000401", // MasterCard PayPass + "A0000000041010", // MasterCard Credit + "A00000000410101213", // MasterCard Credit + "A00000000410101215", // MasterCard Credit + "A0000000042010", // MasterCard Specific + "A0000000043010", // MasterCard Specific + "A0000000043060", // Maestro (Debit) + "A000000004306001", // Maestro (Debit) + "A0000000044010", // MasterCard Specific + "A0000000045010", // MasterCard Specific + "A0000000046000", // Cirrus + "A0000000048002", // SecureCode Auth EMV-CAP + "A0000000049999", // MasterCard PayPass + // American Express + "A000000025", + "A0000000250000", + "A00000002501", + "A000000025010402", + "A000000025010701", + "A000000025010801", + // Groupement des Cartes Bancaires "CB" + "A0000000421010", // Cartes Bancaire EMV Card + "A0000000422010", + "A0000000423010", + "A0000000424010", + "A0000000425010", + // JCB CO., LTD. + "A00000006510", // JCB + "A0000000651010", // JCB J Smart Credit + "A0000001544442", // Banricompras Debito - Banrisul - Banco do Estado do Rio Grande do SUL - S.A. + "F0000000030001", // BRADESCO + "A0000005241010", // RuPay - RuPay + "D5780000021010" // Bankaxept - Bankaxept +}; +const size_t AIDlistLen = sizeof(AIDlist)/sizeof(char*); + +static bool APDULogging = false; +void SetAPDULogging(bool logging) { + APDULogging = logging; +} + static bool print_cb(void *data, const struct tlv *tlv, int level, bool is_leaf) { emv_tag_dump(tlv, stdout, level); if (is_leaf) { @@ -23,7 +94,7 @@ void TLVPrintFromBuffer(uint8_t *data, int datalen) { struct tlvdb *t = NULL; t = tlvdb_parse_multi(data, datalen); if (t) { - PrintAndLog("TLV decoded:"); + PrintAndLog("-------------------- TLV decoded --------------------"); tlvdb_visit(t, print_cb, NULL, 0); tlvdb_free(t); @@ -31,3 +102,362 @@ void TLVPrintFromBuffer(uint8_t *data, int datalen) { PrintAndLog("TLV ERROR: Can't parse response as TLV tree."); } } + +void TLVPrintFromTLV(struct tlvdb *tlv) { + if (!tlv) + return; + + tlvdb_visit(tlv, print_cb, NULL, 0); +} + +void TLVPrintAIDlistFromSelectTLV(struct tlvdb *tlv) { + PrintAndLog("|------------------|--------|-------------------------|"); + PrintAndLog("| AID |Priority| Name |"); + PrintAndLog("|------------------|--------|-------------------------|"); + + struct tlvdb *ttmp = tlvdb_find(tlv, 0x6f); + if (!ttmp) + PrintAndLog("| none |"); + + while (ttmp) { + const struct tlv *tgAID = tlvdb_get_inchild(ttmp, 0x84, NULL); + const struct tlv *tgName = tlvdb_get_inchild(ttmp, 0x50, NULL); + const struct tlv *tgPrio = tlvdb_get_inchild(ttmp, 0x87, NULL); + if (!tgAID) + break; + PrintAndLog("|%s| %s |%s|", + sprint_hex_inrow_ex(tgAID->value, tgAID->len, 18), + (tgPrio) ? sprint_hex(tgPrio->value, 1) : " ", + (tgName) ? sprint_ascii_ex(tgName->value, tgName->len, 25) : " "); + + ttmp = tlvdb_find_next(ttmp, 0x6f); + } + + PrintAndLog("|------------------|--------|-------------------------|"); +} + + +int EMVSelect(bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) { + uint8_t data[APDU_RES_LEN] = {0}; + *ResultLen = 0; + if (sw) *sw = 0; + uint16_t isw = 0; + + // select APDU + data[0] = 0x00; + data[1] = 0xA4; + data[2] = 0x04; + data[3] = 0x00; + data[4] = AIDLen; + memcpy(&data[5], AID, AIDLen); + + if (ActivateField) + DropField(); + + if (APDULogging) + PrintAndLog(">>>> %s", sprint_hex(data, AIDLen + 6)); + + int res = ExchangeAPDU14a(data, AIDLen + 6, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen); + + if (APDULogging) + PrintAndLog("<<<< %s", sprint_hex(Result, *ResultLen)); + + if (res) { + return res; + } + + if (*ResultLen < 2) { + PrintAndLog("SELECT ERROR: returned %d bytes", *ResultLen); + return 5; + } + + *ResultLen -= 2; + isw = Result[*ResultLen] * 0x0100 + Result[*ResultLen + 1]; + if (sw) + *sw = isw; + + if (isw != 0x9000) { + if (APDULogging) + PrintAndLog("SELECT ERROR: [%4X] %s", isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); + return 5; + } + + // add to tlv tree + if (tlv) { + struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen); + tlvdb_add(tlv, t); + } + + return 0; +} + +int EMVSelectPSE(bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t buf[APDU_AID_LEN] = {0}; + *ResultLen = 0; + int len = 0; + int res = 0; + switch (PSENum) { + case 1: + param_gethex_to_eol(PSElist[1], 0, buf, sizeof(buf), &len); + break; + case 2: + param_gethex_to_eol(PSElist[0], 0, buf, sizeof(buf), &len); + break; + default: + return -1; + } + + // select + res = EMVSelect(ActivateField, LeaveFieldON, buf, len, Result, MaxResultLen, ResultLen, sw, NULL); + + return res; +} + +int EMVSearchPSE(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv) { + uint8_t data[APDU_RES_LEN] = {0}; + size_t datalen = 0; + uint16_t sw = 0; + int res; + + // select PPSE + res = EMVSelectPSE(ActivateField, true, 2, data, sizeof(data), &datalen, &sw); + + if (!res){ + struct tlvdb *t = NULL; + t = tlvdb_parse_multi(data, datalen); + if (t) { + int retrycnt = 0; + struct tlvdb *ttmp = tlvdb_find_path(t, (tlv_tag_t[]){0x6f, 0xa5, 0xbf0c, 0x61, 0x00}); + if (!ttmp) + PrintAndLog("PPSE don't have records."); + + while (ttmp) { + const struct tlv *tgAID = tlvdb_get_inchild(ttmp, 0x4f, NULL); + if (tgAID) { + res = EMVSelect(false, true, (uint8_t *)tgAID->value, tgAID->len, data, sizeof(data), &datalen, &sw, tlv); + + // retry if error and not returned sw error + if (res && res != 5) { + if (++retrycnt < 3){ + continue; + } else { + // card select error, proxmark error + if (res == 1) { + PrintAndLog("Exit..."); + return 1; + } + + retrycnt = 0; + PrintAndLog("Retry failed [%s]. Skiped...", sprint_hex_inrow(tgAID->value, tgAID->len)); + } + + // next element + ttmp = tlvdb_find_next(ttmp, 0x61); + continue; + } + retrycnt = 0; + + // all is ok + if (decodeTLV){ + PrintAndLog("%s:", sprint_hex_inrow(tgAID->value, tgAID->len)); + TLVPrintFromBuffer(data, datalen); + } + } + + ttmp = tlvdb_find_next(ttmp, 0x61); + } + + tlvdb_free(t); + } else { + PrintAndLog("PPSE ERROR: Can't get TLV from response."); + } + } else { + PrintAndLog("PPSE ERROR: Can't select PPSE AID. Error: %d", res); + } + + if(!LeaveFieldON) + DropField(); + + return res; +} + +int EMVSearch(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv) { + uint8_t aidbuf[APDU_AID_LEN] = {0}; + int aidlen = 0; + uint8_t data[APDU_RES_LEN] = {0}; + size_t datalen = 0; + uint16_t sw = 0; + + int res = 0; + int retrycnt = 0; + for(int i = 0; i < AIDlistLen; i ++) { + param_gethex_to_eol(AIDlist[i], 0, aidbuf, sizeof(aidbuf), &aidlen); + res = EMVSelect((i == 0) ? ActivateField : false, (i == AIDlistLen - 1) ? LeaveFieldON : true, aidbuf, aidlen, data, sizeof(data), &datalen, &sw, tlv); + // retry if error and not returned sw error + if (res && res != 5) { + if (++retrycnt < 3){ + i--; + } else { + // card select error, proxmark error + if (res == 1) { + PrintAndLog("Exit..."); + return 1; + } + + retrycnt = 0; + PrintAndLog("Retry failed [%s]. Skiped...", AIDlist[i]); + } + continue; + } + retrycnt = 0; + + if (res) + continue; + + if (decodeTLV){ + PrintAndLog("%s:", AIDlist[i]); + TLVPrintFromBuffer(data, datalen); + } + } + + return 0; +} + +int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen) { + // needs to check priority. 0x00 - highest + int prio = 0xffff; + + *AIDlen = 0; + + struct tlvdb *ttmp = tlvdb_find(tlv, 0x6f); + if (!ttmp) + return 1; + + while (ttmp) { + const struct tlv *tgAID = tlvdb_get_inchild(ttmp, 0x84, NULL); + const struct tlv *tgPrio = tlvdb_get_inchild(ttmp, 0x87, NULL); + + if (!tgAID) + break; + + if (tgPrio) { + int pt = bytes_to_num((uint8_t*)tgPrio->value, (tgPrio->len < 2) ? tgPrio->len : 2); + if (pt < prio) { + prio = pt; + + memcpy(AID, tgAID->value, tgAID->len); + *AIDlen = tgAID->len; + } + } else { + // takes the first application from list wo priority + if (!*AIDlen) { + memcpy(AID, tgAID->value, tgAID->len); + *AIDlen = tgAID->len; + } + } + + ttmp = tlvdb_find_next(ttmp, 0x6f); + } + + return 0; +} + +int EMVGPO(bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) { + uint8_t data[APDU_RES_LEN] = {0}; + *ResultLen = 0; + if (sw) *sw = 0; + uint16_t isw = 0; + + // GPO APDU + data[0] = 0x80; + data[1] = 0xA8; + data[2] = 0x00; + data[3] = 0x00; + data[4] = PDOLLen; + if (PDOL) + memcpy(&data[5], PDOL, PDOLLen); + + + if (APDULogging) + PrintAndLog(">>>> %s", sprint_hex(data, PDOLLen + 5)); + + int res = ExchangeAPDU14a(data, PDOLLen + 5, false, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen); + + if (APDULogging) + PrintAndLog("<<<< %s", sprint_hex(Result, *ResultLen)); + + if (res) { + return res; + } + + if (*ResultLen < 2) { + PrintAndLog("GPO ERROR: returned %d bytes", *ResultLen); + return 5; + } + + *ResultLen -= 2; + isw = Result[*ResultLen] * 0x0100 + Result[*ResultLen + 1]; + if (sw) + *sw = isw; + + if (isw != 0x9000) { + if (APDULogging) + PrintAndLog("GPO ERROR: [%4X] %s", isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); + return 5; + } + + // add to tlv tree + if (tlv) { + struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen); + tlvdb_add(tlv, t); + } + + return 0; +} + +int EMVReadRecord(bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) { + uint8_t data[10] = {0}; + *ResultLen = 0; + if (sw) *sw = 0; + uint16_t isw = 0; + + // read record APDU + data[0] = 0x00; + data[1] = 0xb2; + data[2] = SFIrec; + data[3] = (SFI << 3) | 0x04; + data[4] = 0; + + if (APDULogging) + PrintAndLog(">>>> %s", sprint_hex(data, 5)); + + int res = ExchangeAPDU14a(data, 5, false, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen); + + if (APDULogging) + PrintAndLog("<<<< %s", sprint_hex(Result, *ResultLen)); + + if (res) { + return res; + } + + *ResultLen -= 2; + isw = Result[*ResultLen] * 0x0100 + Result[*ResultLen + 1]; + if (sw) + *sw = isw; + + if (isw != 0x9000) { + if (APDULogging) + PrintAndLog("Read record ERROR: [%4X] %s", isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); + return 5; + } + + // add to tlv tree + if (tlv) { + struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen); + tlvdb_add(tlv, t); + } + + return 0; +} + + diff --git a/client/emv/emvcore.h b/client/emv/emvcore.h index 523b92ac..fdd3ab86 100644 --- a/client/emv/emvcore.h +++ b/client/emv/emvcore.h @@ -5,7 +5,7 @@ // at your option, any later version. See the LICENSE.txt file for the text of // the license. //----------------------------------------------------------------------------- -// EMV core functions +// EMV core functionality //----------------------------------------------------------------------------- #ifndef EMVCORE_H__ @@ -15,14 +15,36 @@ #include #include #include +#include #include "util.h" #include "common.h" #include "ui.h" +#include "cmdhf14a.h" +#include "emv/apduinfo.h" #include "emv/tlv.h" +#include "emv/dol.h" #include "emv/dump.h" #include "emv/emv_tags.h" +#define APDU_RES_LEN 260 +#define APDU_AID_LEN 50 + extern void TLVPrintFromBuffer(uint8_t *data, int datalen); +extern void TLVPrintFromTLV(struct tlvdb *tlv); +extern void TLVPrintAIDlistFromSelectTLV(struct tlvdb *tlv); + +extern void SetAPDULogging(bool logging); + +// search application +extern int EMVSearchPSE(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv); +extern int EMVSearch(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv); +extern int EMVSelectPSE(bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int EMVSelect(bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv); +// select application +extern int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen); +// Get Processing Options +extern int EMVGPO(bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv); +extern int EMVReadRecord(bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv); #endif diff --git a/client/emv/tlv.c b/client/emv/tlv.c index 24125cc7..1be91777 100644 --- a/client/emv/tlv.c +++ b/client/emv/tlv.c @@ -299,6 +299,40 @@ void tlvdb_free(struct tlvdb *tlvdb) } } +struct tlvdb *tlvdb_find_next(struct tlvdb *tlvdb, tlv_tag_t tag) { + if (!tlvdb) + return NULL; + + return tlvdb_find(tlvdb->next, tag); +} + +struct tlvdb *tlvdb_find(struct tlvdb *tlvdb, tlv_tag_t tag) { + if (!tlvdb) + return NULL; + + for (; tlvdb; tlvdb = tlvdb->next) { + if (tlvdb->tag.tag == tag) + return tlvdb; + } + + return NULL; +} + +struct tlvdb *tlvdb_find_path(struct tlvdb *tlvdb, tlv_tag_t tag[]) { + int i = 0; + struct tlvdb *tnext = tlvdb; + + while (tnext && tag[i]) { + tnext = tlvdb_find(tnext, tag[i]); + i++; + if (tag[i] && tnext) { + tnext = tnext->children; + } + } + + return tnext; +} + void tlvdb_add(struct tlvdb *tlvdb, struct tlvdb *other) { while (tlvdb->next) { @@ -317,9 +351,8 @@ void tlvdb_visit(const struct tlvdb *tlvdb, tlv_cb cb, void *data, int level) for (; tlvdb; tlvdb = next) { next = tlvdb->next; - bool is_leaf = (tlvdb->children == NULL); - cb(data, &tlvdb->tag, level, is_leaf); - tlvdb_visit(tlvdb->children, cb, data, level+1); + cb(data, &tlvdb->tag, level, (tlvdb->children == NULL)); + tlvdb_visit(tlvdb->children, cb, data, level + 1); } } @@ -356,6 +389,11 @@ const struct tlv *tlvdb_get(const struct tlvdb *tlvdb, tlv_tag_t tag, const stru return NULL; } +const struct tlv *tlvdb_get_inchild(const struct tlvdb *tlvdb, tlv_tag_t tag, const struct tlv *prev) { + tlvdb = tlvdb->children; + return tlvdb_get(tlvdb, tag, prev); +} + unsigned char *tlv_encode(const struct tlv *tlv, size_t *len) { size_t size = tlv->len; diff --git a/client/emv/tlv.h b/client/emv/tlv.h index 5a573566..5b8b6825 100644 --- a/client/emv/tlv.h +++ b/client/emv/tlv.h @@ -39,10 +39,15 @@ struct tlvdb *tlvdb_parse(const unsigned char *buf, size_t len); struct tlvdb *tlvdb_parse_multi(const unsigned char *buf, size_t len); void tlvdb_free(struct tlvdb *tlvdb); +struct tlvdb *tlvdb_find(struct tlvdb *tlvdb, tlv_tag_t tag); +struct tlvdb *tlvdb_find_next(struct tlvdb *tlvdb, tlv_tag_t tag); +struct tlvdb *tlvdb_find_path(struct tlvdb *tlvdb, tlv_tag_t tag[]); + void tlvdb_add(struct tlvdb *tlvdb, struct tlvdb *other); void tlvdb_visit(const struct tlvdb *tlvdb, tlv_cb cb, void *data, int level); const struct tlv *tlvdb_get(const struct tlvdb *tlvdb, tlv_tag_t tag, const struct tlv *prev); +const struct tlv *tlvdb_get_inchild(const struct tlvdb *tlvdb, tlv_tag_t tag, const struct tlv *prev); bool tlv_parse_tl(const unsigned char **buf, size_t *len, struct tlv *tlv); unsigned char *tlv_encode(const struct tlv *tlv, size_t *len); diff --git a/client/util.c b/client/util.c index a1caafdb..92b0e7a6 100644 --- a/client/util.c +++ b/client/util.c @@ -154,6 +154,28 @@ char *sprint_hex(const uint8_t *data, const size_t len) { return buf; } +char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len) { + + int maxLen = ( len > 1024/2) ? 1024/2 : len; + static char buf[1024] = {0}; + char *tmp = buf; + size_t i; + + for (i = 0; i < maxLen; ++i, tmp += 2) + sprintf(tmp, "%02x", (unsigned int) data[i]); + + i *= 2; + int minStrLen = min_str_len > i ? min_str_len : 0; + for(; i < minStrLen; i++, tmp += 1) + sprintf(tmp, " "); + + return buf; +} + +char *sprint_hex_inrow(const uint8_t *data, const size_t len) { + return sprint_hex_inrow_ex(data, len, 0); +} + char *sprint_bin_break(const uint8_t *data, const size_t len, const uint8_t breaks) { // make sure we don't go beyond our char array memory int max_len; @@ -210,7 +232,7 @@ char *sprint_hex_ascii(const uint8_t *data, const size_t len) { return buf; } -char *sprint_ascii(const uint8_t *data, const size_t len) { +char *sprint_ascii_ex(const uint8_t *data, const size_t len, const size_t min_str_len) { static char buf[1024]; char *tmp = buf; memset(buf, 0x00, 1024); @@ -221,9 +243,18 @@ char *sprint_ascii(const uint8_t *data, const size_t len) { tmp[i] = ((c < 32) || (c == 127)) ? '.' : c; ++i; } + + int minStrLen = min_str_len > i ? min_str_len : 0; + for(; i < minStrLen; ++i) + tmp[i] = ' '; + return buf; } +char *sprint_ascii(const uint8_t *data, const size_t len) { + return sprint_ascii_ex(data, len, 0); +} + void num_to_bytes(uint64_t n, size_t len, uint8_t* dest) { while (len--) { @@ -498,8 +529,10 @@ int param_gethex_to_eol(const char *line, int paramnum, uint8_t * data, int maxd int indx = bg; while (line[indx]) { - if (line[indx] == '\t' || line[indx] == ' ') + if (line[indx] == '\t' || line[indx] == ' ') { + indx++; continue; + } if (isxdigit(line[indx])) { buf[strlen(buf) + 1] = 0x00; diff --git a/client/util.h b/client/util.h index 8bab8cfd..e9c48b03 100644 --- a/client/util.h +++ b/client/util.h @@ -37,10 +37,13 @@ extern void FillFileNameByUID(char *fileName, uint8_t * uid, char *ext, int byte extern void print_hex(const uint8_t * data, const size_t len); extern char *sprint_hex(const uint8_t * data, const size_t len); +extern char *sprint_hex_inrow(const uint8_t *data, const size_t len); +extern char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len); extern char *sprint_bin(const uint8_t * data, const size_t len); extern char *sprint_bin_break(const uint8_t *data, const size_t len, const uint8_t breaks); extern char *sprint_hex_ascii(const uint8_t *data, const size_t len); extern char *sprint_ascii(const uint8_t *data, const size_t len); +extern char *sprint_ascii_ex(const uint8_t *data, const size_t len, const size_t min_str_len); extern void num_to_bytes(uint64_t n, size_t len, uint8_t* dest); extern uint64_t bytes_to_num(uint8_t* src, size_t len); -- 2.39.2