diff --git a/CHANGELOG.md b/CHANGELOG.md index df03c68c6..eacaa37d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added to `hf 14a apdu` print apdu and compose apdu (@merlokk) - Change: buggy 'mem read' removed, 'mem save' renamed 'mem dump', can now display too (@doegox) - Fix: timeout for mem wipe was too short, thanks @cjbrigato (@doegox) - Fix 'hf mf sim' - Mifare Classic simulation more flexible anti-collision check (@McEloff) diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index c138aefa1..f92a35370 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -834,20 +834,33 @@ int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool lea static int CmdHF14AAPDU(const char *Cmd) { uint8_t data[PM3_CMD_DATA_SIZE]; int datalen = 0; + uint8_t header[PM3_CMD_DATA_SIZE]; + int headerlen = 0; bool activateField = false; bool leaveSignalON = false; bool decodeTLV = false; + bool decodeAPDU = false; + bool makeAPDU = false; + bool extendedAPDU = false; + int le = 0; CLIParserInit("hf 14a apdu", - "Sends an ISO 7816-4 APDU via ISO 14443-4 block transmission protocol (T=CL)", - "Sample:\n\thf 14a apdu -st 00A404000E325041592E5359532E444446303100\n"); + "Sends an ISO 7816-4 APDU via ISO 14443-4 block transmission protocol (T=CL). works with all apdu types from ISO 7816-4:2013", + "Sample:\n\thf 14a apdu -st 00A404000E325041592E5359532E444446303100\n" + "\thf 14a apdu -sd 00A404000E325041592E5359532E444446303100 - decode apdu\n" + "\thf 14a apdu -sm 00A40400 325041592E5359532E4444463031 -l 256 - encode standard apdu\n" + "\thf 14a apdu -sm 00A40400 325041592E5359532E4444463031 -el 65536 - encode extended apdu\n"); void *argtable[] = { arg_param_begin, - arg_lit0("sS", "select", "activate field and select card"), - arg_lit0("kK", "keep", "leave the signal field ON after receive response"), - arg_lit0("tT", "tlv", "executes TLV decoder if it possible"), - arg_strx1(NULL, NULL, "", NULL), + arg_lit0("sS", "select", "activate field and select card"), + arg_lit0("kK", "keep", "leave the signal field ON after receive response"), + arg_lit0("tT", "tlv", "executes TLV decoder if it possible"), + arg_lit0("dD", "decapdu", "decode apdu request if it possible"), + arg_str0("mM", "make", "", "make apdu with head from this field and data from data field. Must be 4 bytes length: "), + arg_lit0("eE", "extended", "make extended length apdu if `m` parameter included"), + arg_int0("lL", "le", "", "Le apdu parameter if `m` parameter included"), + arg_strx1(NULL, NULL, "", "data if `m` parameter included"), arg_param_end }; CLIExecWithReturn(Cmd, argtable, false); @@ -855,12 +868,66 @@ static int CmdHF14AAPDU(const char *Cmd) { activateField = arg_get_lit(1); leaveSignalON = arg_get_lit(2); decodeTLV = arg_get_lit(3); - // len = data + PCB(1b) + CRC(2b) - CLIGetHexBLessWithReturn(4, data, &datalen, 1 + 2); + decodeAPDU = arg_get_lit(4); + + CLIGetHexWithReturn(5, header, &headerlen); + makeAPDU = headerlen > 0; + if (makeAPDU && headerlen != 4) { + PrintAndLogEx(ERR, "header length must be 4 bytes instead of %d", headerlen); + return 1; + } + extendedAPDU = arg_get_lit(6); + le = arg_get_int_def(7, 0); + + if (makeAPDU) { + uint8_t apdudata[PM3_CMD_DATA_SIZE] = {0}; + int apdudatalen = 0; + + CLIGetHexBLessWithReturn(8, apdudata, &apdudatalen, 1 + 2); + + APDUStruct apdu; + apdu.cla = header[0]; + apdu.ins = header[1]; + apdu.p1 = header[2]; + apdu.p2 = header[3]; + + apdu.lc = apdudatalen; + apdu.data = apdudata; + + apdu.extended_apdu = extendedAPDU; + apdu.le = le; + + if (APDUEncode(&apdu, data, &datalen)) { + PrintAndLogEx(ERR, "can't make apdu with provided parameters."); + return 2; + } + + } else { + if (extendedAPDU) { + PrintAndLogEx(ERR, "make mode not set but here `e` option."); + return 3; + } + if (le > 0) { + PrintAndLogEx(ERR, "make mode not set but here `l` option."); + return 3; + } + + // len = data + PCB(1b) + CRC(2b) + CLIGetHexBLessWithReturn(8, data, &datalen, 1 + 2); + } CLIParserFree(); PrintAndLogEx(NORMAL, ">>>>[%s%s%s] %s", activateField ? "sel " : "", leaveSignalON ? "keep " : "", decodeTLV ? "TLV" : "", sprint_hex(data, datalen)); + if (decodeAPDU) { + APDUStruct apdu; + + if (APDUDecode(data, datalen, &apdu) == 0) + APDUPrint(apdu); + else + PrintAndLogEx(WARNING, "can't decode APDU."); + } + int res = ExchangeAPDU14a(data, datalen, activateField, leaveSignalON, data, PM3_CMD_DATA_SIZE, &datalen); if (res) diff --git a/client/emv/apduinfo.c b/client/emv/apduinfo.c index df44ef961..469078456 100644 --- a/client/emv/apduinfo.c +++ b/client/emv/apduinfo.c @@ -314,3 +314,166 @@ const char *GetAPDUCodeDescription(uint8_t sw1, uint8_t sw2) { else return APDUCodeTable[0].Description; //empty string } + +int APDUDecode(uint8_t *data, int len, APDUStruct *apdu) { + ExtAPDUHeader *hapdu = (ExtAPDUHeader *)data; + + apdu->cla = hapdu->cla; + apdu->ins = hapdu->ins; + apdu->p1 = hapdu->p1; + apdu->p2 = hapdu->p2; + + apdu->lc = 0; + apdu->data = NULL; + apdu->le = 0; + apdu->extended_apdu = false; + apdu->case_type = 0x00; + + uint8_t b0 = hapdu->lc[0]; + + // case 1 + if (len == 4) { + apdu->case_type = 0x01; + } + + // case 2S (Le) + if (len == 5) { + apdu->case_type = 0x02; + apdu->le = b0; + if (!apdu->le) + apdu->le = 0x100; + } + + // case 3S (Lc + data) + if (len == 5U + b0 && b0 != 0) { + apdu->case_type = 0x03; + apdu->lc = b0; + } + + // case 4S (Lc + data + Le) + if (len == 5U + b0 + 1U && b0 != 0) { + apdu->case_type = 0x04; + apdu->lc = b0; + apdu->le = data[len - 1]; + if (!apdu->le) + apdu->le = 0x100; + } + + // extended length apdu + if (len >= 7 && b0 == 0) { + uint16_t extlen = (hapdu->lc[1] << 8) + hapdu->lc[2]; + + // case 2E (Le) - extended + if (len == 7) { + apdu->case_type = 0x12; + apdu->extended_apdu = true; + apdu->le = extlen; + if (!apdu->le) + apdu->le = 0x10000; + } + + // case 3E (Lc + data) - extended + if (len == 7U + extlen) { + apdu->case_type = 0x13; + apdu->extended_apdu = true; + apdu->lc = extlen; + } + + // case 4E (Lc + data + Le) - extended 2-byte Le + if (len == 7U + extlen + 2U) { + apdu->case_type = 0x14; + apdu->extended_apdu = true; + apdu->lc = extlen; + apdu->le = (data[len - 2] << 8) + data[len - 1]; + if (!apdu->le) + apdu->le = 0x10000; + } + + // case 4E (Lc + data + Le) - extended 3-byte Le + if (len == 7U + extlen + 3U && data[len - 3] == 0) { + apdu->case_type = 0x24; + apdu->extended_apdu = true; + apdu->lc = extlen; + apdu->le = (data[len - 2] << 8) + data[len - 1]; + if (!apdu->le) + apdu->le = 0x10000; + } + } + + if (!apdu->case_type) + return 1; + + if (apdu->lc) { + if (apdu->extended_apdu) { + apdu->data = data + 7; + } else { + apdu->data = data + 5; + } + + } + + return 0; +} + +int APDUEncode(APDUStruct *apdu, uint8_t *data, int *len) { + if (len) + *len = 0; + + if (apdu->le > 0x10000 || apdu->lc > 0xffff) + return 1; + + size_t dptr = 0; + data[dptr++] = apdu->cla; + data[dptr++] = apdu->ins; + data[dptr++] = apdu->p1; + data[dptr++] = apdu->p2; + + if (apdu->lc) { + if (apdu->extended_apdu || apdu->lc > 0xff || apdu->le > 0x100) { + data[dptr++] = 0x00; + data[dptr++] = (apdu->lc >> 8) & 0xff; + data[dptr++] = (apdu->lc) & 0xff; + memmove(&data[dptr], apdu->data, apdu->lc); + dptr += apdu->lc; + apdu->extended_apdu = true; + } else { + data[dptr++] = apdu->lc; + memmove(&data[dptr], apdu->data, apdu->lc); + dptr += apdu->lc; + } + } + + if (apdu->le) { + if (apdu->extended_apdu) { + if (apdu->le != 0x10000) { + data[dptr++] = 0x00; + data[dptr++] = (apdu->le >> 8) & 0xff; + data[dptr++] = (apdu->le) & 0xff; + } else { + data[dptr++] = 0x00; + data[dptr++] = 0x00; + data[dptr++] = 0x00; + } + } else { + if (apdu->le != 0x100) + data[dptr++] = apdu->le; + else + data[dptr++] = 0x00; + } + } + + if (len) + *len = dptr; + return 0; +} + +void APDUPrint(APDUStruct apdu) { + APDUPrintEx(apdu, 0); +} + +void APDUPrintEx(APDUStruct apdu, size_t maxdatalen) { + PrintAndLogEx(INFO, "APDU: %scase=0x%02x cla=0x%02x ins=0x%02x p1=0x%02x p2=0x%02x Lc=0x%02x(%d) Le=0x%02x(%d)", + apdu.extended_apdu ? "[e]" : "", apdu.case_type, apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.lc, apdu.lc, apdu.le, apdu.le); + if (maxdatalen > 0) + PrintAndLogEx(INFO, "data: %s%s", sprint_hex(apdu.data, MIN(apdu.lc, maxdatalen)), apdu.lc > maxdatalen ? "..." : ""); +} diff --git a/client/emv/apduinfo.h b/client/emv/apduinfo.h index 317d661ff..b81877366 100644 --- a/client/emv/apduinfo.h +++ b/client/emv/apduinfo.h @@ -14,8 +14,11 @@ #include #include #include +#include #include +#include "util.h" + #define APDUCODE_TYPE_NONE 0 #define APDUCODE_TYPE_INFO 1 #define APDUCODE_TYPE_WARNING 2 @@ -31,4 +34,29 @@ typedef struct { const APDUCode *GetAPDUCode(uint8_t sw1, uint8_t sw2); const char *GetAPDUCodeDescription(uint8_t sw1, uint8_t sw2); +typedef struct { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t lc[3]; +} PACKED ExtAPDUHeader; + +typedef struct { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint16_t lc; + uint8_t *data; + uint32_t le; + bool extended_apdu; + uint8_t case_type; +} PACKED APDUStruct; + +extern int APDUDecode(uint8_t *data, int len, APDUStruct *apdu); +extern int APDUEncode(APDUStruct *apdu, uint8_t *data, int *len); +extern void APDUPrint(APDUStruct apdu); +extern void APDUPrintEx(APDUStruct apdu, size_t maxdatalen); + #endif diff --git a/client/util.h b/client/util.h index b2c84f4e5..3d2de1dc7 100644 --- a/client/util.h +++ b/client/util.h @@ -21,6 +21,12 @@ #include "ui.h" // PrintAndLog #include "commonutil.h" +#ifdef _MSC_VER +#define PACKED +#else +#define PACKED __attribute__((packed)) +#endif + #ifdef ANDROID #include #endif