diff --git a/client/Makefile b/client/Makefile index 2b5e9ae6b..b2e6e5599 100644 --- a/client/Makefile +++ b/client/Makefile @@ -57,7 +57,8 @@ endif LUAPLATFORM = generic ifneq (,$(findstring MINGW,$(platform))) - LUAPLATFORM = mingw + LUAPLATFORM = mingw + LDLIBS += -lws2_32 else ifeq ($(platform),Darwin) LUAPLATFORM = macosx @@ -134,6 +135,7 @@ CMDSRCS = $(SRC_SMARTCARD) \ fido/cose.c \ fido/cbortools.c \ fido/fidocore.c \ + ksx6924/ksx6924core.c \ mifare/mfkey.c \ loclass/cipher.c \ loclass/cipherutils.c \ @@ -188,6 +190,7 @@ CMDSRCS = $(SRC_SMARTCARD) \ hardnested/hardnested_bruteforce.c \ cmdhftopaz.c \ cmdhffido.c \ + cmdhfksx6924.c \ cmdhw.c \ cmdlf.c \ cmdlfawid.c \ diff --git a/client/cmdhf.c b/client/cmdhf.c index 6d25cac0a..4f68e14a5 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -21,6 +21,7 @@ #include "cmdhf14b.h" #include "cmdhf15.h" #include "cmdhfepa.h" +#include "cmdhfksx6924.h" #include "cmdhflegic.h" #include "cmdhficlass.h" #include "cmdhfmf.h" @@ -141,6 +142,7 @@ static command_t CommandTable[] = {"14b", CmdHF14B, 0, "{ ISO14443B RFIDs... }"}, {"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"}, {"epa", CmdHFEPA, 0, "{ German Identification Card... }"}, + {"ksx6924", CmdHFKSX6924, 0, "{ KS X 6924 (T-Money, Snapper+) RFIDs... }"}, {"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"}, {"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"}, {"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"}, diff --git a/client/cmdhfksx6924.c b/client/cmdhfksx6924.c new file mode 100644 index 000000000..9a072d17e --- /dev/null +++ b/client/cmdhfksx6924.c @@ -0,0 +1,305 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// 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. +//----------------------------------------------------------------------------- +// Commands for KS X 6924 transit cards (T-Money, Snapper+) +//----------------------------------------------------------------------------- +// This is used in T-Money (South Korea) and Snapper plus (Wellington, New +// Zealand). +// +// References: +// - https://github.com/micolous/metrodroid/wiki/T-Money (in English) +// - https://github.com/micolous/metrodroid/wiki/Snapper (in English) +// - https://kssn.net/StdKS/ks_detail.asp?k1=X&k2=6924-1&k3=4 +// (KS X 6924, only available in Korean) +// - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf +// (TTAK.KO 12.0240, only available in Korean) +//----------------------------------------------------------------------------- + + +#include "cmdhfksx6924.h" + +#include +#include +#include +#include +#include +#include +#include "comms.h" +#include "cmdmain.h" +#include "util.h" +#include "ui.h" +#include "proxmark3.h" +#include "cliparser/cliparser.h" +#include "ksx6924/ksx6924core.h" +#include "emv/tlv.h" +#include "emv/apduinfo.h" +#include "cmdhf14a.h" + +static int CmdHelp(const char *Cmd); + +void getAndPrintBalance() { + uint32_t balance; + bool ret = KSX6924GetBalance(&balance); + if (!ret) { + PrintAndLog("Error getting balance"); + return; + } + + PrintAndLog("Current balance: %ld won/cents", balance); +} + +int CmdHFKSX6924Balance(const char* cmd) { + CLIParserInit("hf ksx6924 balance", + "Gets the current purse balance.\n", + "Usage:\n\thf ksx6924 balance\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + + CLIParserFree(); + SetAPDULogging(APDULogging); + + bool ret = KSX6924TrySelect(); + if (!ret) { + goto end; + } + + getAndPrintBalance(); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +int CmdHFKSX6924Info(const char *cmd) { + CLIParserInit("hf ksx6924 info", + "Get info about a KS X 6924 transit card.\nThis application is used by T-Money (South Korea) and Snapper+ (Wellington, New Zealand).\n", + "Usage:\n\thf ksx6924 info\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + + CLIParserFree(); + SetAPDULogging(APDULogging); + + // KSX6924 info + uint8_t buf[APDU_RESPONSE_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = KSX6924Select(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + if (!leaveSignalON) { + DropField(); + } + return res; + } + + if (sw != 0x9000) { + if (sw) { + PrintAndLog("Not a KS X 6924 card! APDU response: %04x - %s", + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } else { + PrintAndLog("APDU exchange error. Card returns 0x0000."); + } + goto end; + } + + + // PrintAndLog("APDU response: %s", sprint_hex(buf, len)); + + // FCI Response is a BER-TLV, we are interested in tag 6F,B0 only. + const uint8_t* p = buf; + struct tlv fci_tag; + + while (len > 0) { + memset(&fci_tag, 0, sizeof(fci_tag)); + bool ret = tlv_parse_tl(&p, &len, &fci_tag); + + if (!ret) { + PrintAndLog("Error parsing FCI!"); + goto end; + } + + // PrintAndLog("tag %02x, len %d, value %s", + // fci_tag.tag, fci_tag.len, + // sprint_hex(p, fci_tag.len)); + + if (fci_tag.tag == 0x6f) { /* FCI template */ + break; + } else { + p += fci_tag.len; + continue; + } + } + + if (fci_tag.tag != 0x6f) { + PrintAndLog("Couldn't find tag 6F (FCI) in SELECT response"); + goto end; + } + + // We now are at Tag 6F (FCI template), get Tag B0 inside of it + while (len > 0) { + memset(&fci_tag, 0, sizeof(fci_tag)); + bool ret = tlv_parse_tl(&p, &len, &fci_tag); + + if (!ret) { + PrintAndLog("Error parsing FCI!"); + goto end; + } + + // PrintAndLog("tag %02x, len %d, value %s", + // fci_tag.tag, fci_tag.len, + // sprint_hex(p, fci_tag.len)); + + if (fci_tag.tag == 0xb0) { /* KS X 6924 purse info */ + break; + } else { + p += fci_tag.len; + continue; + } + } + + if (fci_tag.tag != 0xb0) { + PrintAndLog("Couldn't find tag B0 (KS X 6924 purse info) in FCI"); + goto end; + } + + struct ksx6924_purse_info purseInfo; + bool ret = KSX6924ParsePurseInfo(p, fci_tag.len, &purseInfo); + + if (!ret) { + PrintAndLog("Error parsing KS X 6924 purse info"); + goto end; + } + + KSX6924PrintPurseInfo(&purseInfo); + + getAndPrintBalance(); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +int CmdHFKSX6924Select(const char *cmd) { + CLIParserInit("hf ksx6924 select", + "Selects KS X 6924 application, and leaves field up.\n", + "Usage:\n\thf ksx6924 select\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + CLIParserFree(); + SetAPDULogging(APDULogging); + + bool ret = KSX6924TrySelect(); + if (ret) { + PrintAndLog("OK"); + } else { + // Wrong app, drop field. + DropField(); + } + + return 0; +} + +int CmdHFKSX6924PRec(const char *cmd) { + CLIParserInit("hf ksx6924 prec", + "Executes proprietary read record command.\nData format is unknown. Other records are available with 'emv getrec'.\n", + "Usage:\n\thf ksx6924 prec 0b -> read proprietary record 0x0b\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_strx1(NULL, NULL, "", NULL), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + uint8_t data[APDU_RESPONSE_LEN] = {0}; + int datalen = 0; + CLIGetHexWithReturn(3, data, &datalen); + CLIParserFree(); + SetAPDULogging(APDULogging); + + if (datalen != 1) { + PrintAndLog("Record parameter must be 1 byte long (eg: 0f)"); + goto end; + } + + bool ret = KSX6924TrySelect(); + if (!ret) { + goto end; + } + + PrintAndLog("Getting record %02x...", data[0]); + uint8_t recordData[0x10]; + if (!KSX6924ProprietaryGetRecord(data[0], recordData, sizeof(recordData))) { + PrintAndLog("Error getting record"); + goto end; + } + + PrintAndLog(" %s", sprint_hex(recordData, sizeof(recordData))); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +static command_t CommandTable[] = +{ + {"help", CmdHelp, 1, "This help."}, + {"info", CmdHFKSX6924Info, 0, "Get info about a KS X 6924 (T-Money, Snapper+) transit card"}, + {"select", CmdHFKSX6924Select, 0, "Select application, and leave field up"}, + {"balance", CmdHFKSX6924Balance, 0, "Get current purse balance"}, + {"prec", CmdHFKSX6924PRec, 0, "Send proprietary get record command (CLA=90, INS=4C)"}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFKSX6924(const char *Cmd) { + (void)WaitForResponseTimeout(CMD_ACK,NULL,100); + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +} + diff --git a/client/cmdhfksx6924.h b/client/cmdhfksx6924.h new file mode 100644 index 000000000..ce17a124c --- /dev/null +++ b/client/cmdhfksx6924.h @@ -0,0 +1,18 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// 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. +//----------------------------------------------------------------------------- +// Commands for KS X 6924 transit cards (T-Money, Snapper+) +//----------------------------------------------------------------------------- + +#ifndef CMDHFKSX6924_H__ +#define CMDHFKSX6924_H__ + +extern int CmdHFKSX6924(const char *Cmd); + + +#endif /* CMDHFKSX6924_H__ */ diff --git a/client/emv/emvcore.c b/client/emv/emvcore.c index 46fc1b1e9..59b984c13 100644 --- a/client/emv/emvcore.c +++ b/client/emv/emvcore.c @@ -132,6 +132,7 @@ static const TAIDList AIDlist [] = { { CV_OTHER, "A0000006723020" }, // TROY - Turkey - TROY chip debit card - Turkey's Payment Method { CV_OTHER, "A0000007705850" }, // Indian Oil Corporation Limited - India - XTRAPOWER Fleet Card Program - Indian Oil’s Pre Paid Program { CV_OTHER, "D27600002545500100" }, // ZKA - Germany - Girocard - ZKA Girocard (Geldkarte) (Germany) + { CV_OTHER, "D4100000030001" }, // KS X 6924 (T-Money, South Korea and Snapper+, Wellington, New Zealand) { CV_OTHER, "D5280050218002" }, // The Netherlands - ? - (Netherlands) { CV_OTHER, "D5780000021010" }, // Bankaxept Norway Bankaxept Norwegian domestic debit card { CV_OTHER, "F0000000030001" }, // BRADESCO - Brazilian Bank Banco Bradesco diff --git a/client/ksx6924/ksx6924core.c b/client/ksx6924/ksx6924core.c new file mode 100644 index 000000000..c678f1c11 --- /dev/null +++ b/client/ksx6924/ksx6924core.c @@ -0,0 +1,489 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// 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. +//----------------------------------------------------------------------------- +// KS X 6924 (T-Money, Snapper+) protocol implementation +//----------------------------------------------------------------------------- +// This is used in T-Money (South Korea) and Snapper plus (Wellington, New +// Zealand). +// +// References: +// - https://github.com/micolous/metrodroid/wiki/T-Money (in English) +// - https://github.com/micolous/metrodroid/wiki/Snapper (in English) +// - https://kssn.net/en/search/stddetail.do?itemNo=K001010104929 +// (KS X 6924, only available in Korean) +// - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf +// (TTAK.KO 12.0240, only available in Korean) +//----------------------------------------------------------------------------- + +#include "ksx6924core.h" +#ifdef _WIN32 +# include // ntohl +#else +# include // ntohl +#endif +#include +#include "emv/emvcore.h" +#include "emv/apduinfo.h" +#include "emv/dump.h" +#include "fido/fidocore.h" // FIDOExchange +#include "protocols.h" +#include "ui.h" +#include "util.h" +#include "usb_cmd.h" + +// Date type. This is the actual on-card format. +typedef struct { + uint8_t year[2]; // bcd + uint8_t month[1]; // bcd + uint8_t day[1]; // bcd +} PACKED _ksx6924_internal_date_t; + +// Purse information (FCI tag b0). This is the actual on-card format. +typedef struct { + uint8_t cardType; + uint8_t alg; + uint8_t vk; + uint8_t idCenter; + uint8_t csn[8]; // bcd + uint8_t idtr[5]; // bcd + _ksx6924_internal_date_t issueDate; + _ksx6924_internal_date_t expiryDate; + uint8_t userCode; + uint8_t disRate; + uint8_t balMax[4]; // uint32_t big-endian + uint8_t bra[2]; // bcd + uint8_t mmax[4]; // uint32_t big-endian + uint8_t tcode; + uint8_t ccode; + uint8_t rfu[8]; +} PACKED _ksx6924_internal_purse_info_t; + +// Declares a structure for simple enums. +#define MAKE_ENUM_TYPE(KEY_TYPE) \ + struct _ksx6924_enum_ ## KEY_TYPE { \ + KEY_TYPE key; \ + const char *value; \ + }; \ + static int _ksx6924_ ## KEY_TYPE ## _enum_compare( \ + const void *a, const void *b) { \ + const KEY_TYPE *needle = a; \ + const struct _ksx6924_enum_ ## KEY_TYPE *candidate = b; \ + return (*needle) - (candidate->key); \ + } + +// Declares a enum, and builds a KSX6924Lookup* function to point to it. +#define MAKE_ENUM_CONST(NAME, KEY_TYPE, VALS...) \ + static const struct _ksx6924_enum_ ## KEY_TYPE KSX6924_ENUM_ ## NAME [] = { \ + VALS \ + }; \ + const char* KSX6924Lookup ## NAME ( \ + KEY_TYPE key, const char* defaultValue) { \ + struct _ksx6924_enum_ ## KEY_TYPE *r = bsearch( \ + &key, KSX6924_ENUM_ ## NAME, \ + sizeof(KSX6924_ENUM_ ## NAME) / sizeof(KSX6924_ENUM_ ## NAME [0]), \ + sizeof(KSX6924_ENUM_ ## NAME [0]), \ + _ksx6924_ ## KEY_TYPE ## _enum_compare); \ + if (r == NULL) { \ + return defaultValue; \ + } \ + return r->value; \ + } + +MAKE_ENUM_TYPE(uint8_t); + +// KSX6924LookupCardType +MAKE_ENUM_CONST(CardType, uint8_t, + { 0x00, "Pre-paid" }, + { 0x10, "Post-pay" }, + { 0x20, "Mobile post-pay" }, +); + +// KSX6924LookupAlg +MAKE_ENUM_CONST(Alg, uint8_t, + { 0x00, "SEED" }, + { 0x10, "3DES" }, +); + +// KSX6924LookupTMoneyIDCenter +MAKE_ENUM_CONST(TMoneyIDCenter, uint8_t, + { 0x00, "reserved" }, + { 0x01, "Korea Financial Telecommunications and Clearings Institute" }, + { 0x02, "A-Cash ?? 에이캐시" }, // FIXME: translation + { 0x03, "Mybi" }, + + { 0x05, "V-Cash ?? 브이캐시" }, // FIXME: translation + { 0x06, "Mondex Korea" }, + { 0x07, "Korea Expressway Corporation" }, + { 0x08, "Korea Smart Card Corporation" }, + { 0x09, "KORAIL Networks" }, + + { 0x0b, "EB Card Corporation" }, + { 0x0c, "Seoul Bus Transport Association" }, + { 0x0d, "Cardnet" }, +); + +// KSX6924LookupTMoneyUserCode +MAKE_ENUM_CONST(TMoneyUserCode, uint8_t, + { 0x01, "Regular/normal" }, + { 0x02, "Child" }, + + { 0x04, "Youth" }, + + { 0x06, "Route ?? 경로" }, // FIXME: "route" doesn't make sense, documentation error? + + { 0x0f, "Test" }, + { 0xff, "Inactive" }, +); + +// KSX6924LookupTMoneyDisRate +MAKE_ENUM_CONST(TMoneyDisRate, uint8_t, + { 0x00, "No discount" }, + + { 0x10, "Disabled, basic" }, + { 0x11, "Disabled, companion" }, + + { 0x20, "Well, basic ?? 유공, 기본" }, // FIXME: "well" doesn't make sense? + { 0x21, "Well, companion ?? 유공, 동반 무임" }, // FIXME: "well" doesn't make sense? +); + +// KSX6924LookupTMoneyTCode +MAKE_ENUM_CONST(TMoneyTCode, uint8_t, + { 0x00, "None" }, + { 0x01, "SK Telecom" }, + { 0x02, "Korea Telecom" }, + { 0x03, "LG Uplus" }, +); + +// KSX6924LookupTMoneyCCode +MAKE_ENUM_CONST(TMoneyCCode, uint8_t, + { 0x00, "None" }, + { 0x01, "KB Kookmin Bank" }, + { 0x02, "Nonghyup Bank" }, + { 0x03, "Lotte Card" }, + { 0x04, "BC Card" }, + { 0x05, "Samsung Card" }, + { 0x06, "Shinhan Bank" }, + { 0x07, "Citibank Korea" }, + { 0x08, "Korea Exchange Bank" }, + { 0x09, "우리" }, // FIXME: translation + { 0x0a, "Hana SK Card" }, + { 0x0b, "Hyundai Capital Services" }, +); + +static const char* KSX6924_UNKNOWN = "Unknown"; + +/** + * Converts a single byte in binary-coded decimal format to an integer. + * + * Expected return values are between 0-99 inclusive. + * + * Returns -1 on invalid input. + * + * Examples: + * bcdToInteger(0x35) = 35 (decimal) + * bcdToInteger(0x58) = 58 (decimal) + * bcdToInteger(0xf4) = -1 (invalid) + */ +int16_t bcdToInteger(const uint8_t i) { + uint16_t high = ((i & 0xf0) >> 4) * 10; + uint16_t low = (i & 0xf); + + if (high >= 100 || low >= 10) { + // Invalid + return -1; + } + + return high + low; +} + + +/** + * Converts multiple bytes in binary-coded decimal format to an integer. + * + * Expected return values are 0-(100^len). + * + * Returns -1 on invalid input. + * + * Example: + * bcdToLong({0x12, 0x34}, 2) = 1234 (decimal) + */ +int64_t bcdToLong(const uint8_t *buf, size_t len) { + int64_t o = 0; + for (int i = 0; i < len; i++) { + int16_t t = bcdToInteger(buf[i]); + if (t < 0) { + // invalid + return -1; + } + + o = (o * 100) + t; + } + + return o; +} + + +/** + * Converts a date from on-card format to ksx6924_date format. + */ +bool convertInternalDate( + const _ksx6924_internal_date_t i, struct ksx6924_date* ret) { + int64_t year = bcdToLong(i.year, 2); + int16_t month = bcdToInteger(i.month[0]); + int16_t day = bcdToInteger(i.day[0]); + + if (year < 0 || year > 0xffff || month < 0 || day < 0) { + goto fail; + } + + ret->year = year & 0xffff; + ret->month = month; + ret->day = day; + return true; + +fail: + memset(ret, 0, sizeof(struct ksx6924_date)); + return false; +} + + +/** + * Parses purse info in FCI tag b0 + */ +bool KSX6924ParsePurseInfo(const uint8_t *purseInfo, size_t purseLen, + struct ksx6924_purse_info* ret) { + if (purseLen != sizeof(_ksx6924_internal_purse_info_t)) { + // Invalid size! + PrintAndLog("Expected %ld bytes, got %ld\n", + sizeof(_ksx6924_internal_purse_info_t), purseLen); + goto fail; + } + + const _ksx6924_internal_purse_info_t* internalPurseInfo = (const _ksx6924_internal_purse_info_t*)purseInfo; + + memset(ret, 0, sizeof(struct ksx6924_purse_info)); + + // Simple copies + ret->cardType = internalPurseInfo->cardType; + ret->alg = internalPurseInfo->alg; + ret->vk = internalPurseInfo->vk; + ret->idCenter = internalPurseInfo->idCenter; + ret->userCode = internalPurseInfo->userCode; + ret->disRate = internalPurseInfo->disRate; + ret->tcode = internalPurseInfo->tcode; + ret->ccode = internalPurseInfo->ccode; + + // Fields that need rewriting + hex_to_buffer(ret->csn, internalPurseInfo->csn, + sizeof(internalPurseInfo->csn), sizeof(ret->csn) - 1, + /* min_str_len */ 0, /* spaces_between */ 0, + /* uppercase */ false); + + int64_t idtr = bcdToLong(internalPurseInfo->idtr, 5); + if (idtr < 0) { + idtr = 0; // fail + } + ret->idtr = idtr; + + int64_t bra = bcdToLong(internalPurseInfo->bra, 2); + if (bra < 0) { + bra = 0; // fail + } + ret->bra = bra & 0xffff; + + convertInternalDate(internalPurseInfo->issueDate, &(ret->issueDate)); + convertInternalDate(internalPurseInfo->expiryDate, &(ret->expiryDate)); + + ret->balMax = ntohl(*(uint32_t*)(internalPurseInfo->balMax)); + ret->mmax = ntohl(*(uint32_t*)(internalPurseInfo->mmax)); + memcpy(&ret->rfu, &internalPurseInfo->rfu, 8); + + // TODO + return true; + +fail: + memset(ret, 0, sizeof(struct ksx6924_purse_info)); + return false; +}; + +/** + * Prints out a ksx6924_purse_info + */ +void KSX6924PrintPurseInfo(const struct ksx6924_purse_info *purseInfo) { + if (purseInfo == NULL) { + return; + } + + PrintAndLog("## KS X 6924 Purse Info:"); + PrintAndLog(""); + PrintAndLog("cardType .............................. %02x (%s)", + purseInfo->cardType, + KSX6924LookupCardType(purseInfo->cardType, KSX6924_UNKNOWN)); + PrintAndLog("alg (encryption algorithm) ............ %02x (%s)", + purseInfo->alg, + KSX6924LookupAlg(purseInfo->alg, KSX6924_UNKNOWN)); + PrintAndLog("vk (keyset version) ................... %02x", + purseInfo->vk); + PrintAndLog("idCenter (issuer ID) .................. %02x (%s)", + purseInfo->idCenter, + KSX6924LookupTMoneyIDCenter(purseInfo->idCenter, KSX6924_UNKNOWN)); + PrintAndLog("csn (card number) ..................... %s", + purseInfo->csn); + PrintAndLog("idtr (card usage authentication ID) ... %i", + purseInfo->idtr); + PrintAndLog("issueDate ............................. %04i-%02i-%02i", + purseInfo->issueDate.year, purseInfo->issueDate.month, purseInfo->issueDate.day); + PrintAndLog("expiryDate ............................ %04i-%02i-%02i", + purseInfo->expiryDate.year, purseInfo->expiryDate.month, purseInfo->expiryDate.day); + PrintAndLog("userCode (ticket type) ................ %02x (%s)", + purseInfo->userCode, + KSX6924LookupTMoneyUserCode(purseInfo->userCode, KSX6924_UNKNOWN)); + PrintAndLog("disRate (discount type) ............... %02x (%s)", + purseInfo->disRate, + KSX6924LookupTMoneyDisRate(purseInfo->disRate, KSX6924_UNKNOWN)); + PrintAndLog("balMax (in won/cents) ................. %ld", + purseInfo->balMax); + PrintAndLog("bra (branch code) ..................... %04x", + purseInfo->bra); + PrintAndLog("mmax (one-time transaction limit) ..... %ld", + purseInfo->mmax); + PrintAndLog("tcode (telecom carrier ID) ............ %02x (%s)", + purseInfo->tcode, + KSX6924LookupTMoneyTCode(purseInfo->tcode, KSX6924_UNKNOWN)); + PrintAndLog("ccode (credit card company ID) ........ %02x (%s)", + purseInfo->ccode, + KSX6924LookupTMoneyCCode(purseInfo->ccode, KSX6924_UNKNOWN)); + PrintAndLog("rfu (reserved) ........................ %s", + sprint_hex(purseInfo->rfu, sizeof(purseInfo->rfu))); + PrintAndLog(""); +} + + +/** + * Selects the KS X 6924 Application, D4100000030001, and returns the response + * data. + */ +int KSX6924Select( + bool ActivateField, bool LeaveFieldON, + uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t aid[] = {0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01}; + + return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, aid, + sizeof(aid), Result, MaxResultLen, ResultLen, sw, NULL); +} + + +/** + * Selects the KS X 6924 Application. Returns true if selected successfully. + */ +bool KSX6924TrySelect() { + uint8_t buf[APDU_RESPONSE_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = KSX6924Select(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + DropField(); + return false; + } + + if (sw != 0x9000) { + if (sw) { + PrintAndLog("Not a KS X 6924 card! APDU response: %04x - %s", + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } else { + PrintAndLog("APDU exchange error. Card returns 0x0000."); + } + return false; + } + + return true; +} + + +/** + * Gets the balance from a KS X 6924 card. + */ +bool KSX6924GetBalance(uint32_t* result) { + if (result == NULL) { + return false; + } + + uint16_t sw; + size_t result_len; + uint8_t rawResult[4 /* message */ + 2 /* sw */]; + uint8_t apdu[] = {0x90, 0x4c, 0x00, 0x00, 4}; + int res = FIDOExchange(apdu, sizeof(apdu), + rawResult, sizeof(rawResult), + &result_len, &sw); + if (res) { + // error communicating + goto fail; + } + + if (sw != 0x9000) { + // card returned error + goto fail; + } + + *result = ntohl(*(uint32_t*)(rawResult)); + return true; + +fail: + *result = 0; + return false; +} + + +/** + * Issues a proprietary "get record" command (CLA=90, INS=4C). + * + * The function of these records is not known, but they are present on KS X + * 6924 cards and used by the official mobile apps. + * + * result must be a buffer of 16 bytes. The card will only respond to 16 byte + * requests. + * + * Returns false on error. + */ +bool KSX6924ProprietaryGetRecord(uint8_t id, uint8_t* result, + size_t resultLen) { + if (result == NULL) { + return false; + } + memset(result, 0, resultLen); + + uint16_t sw; + size_t result_len; + + uint8_t rawResult[resultLen /* message */ + 2 /* sw */]; + memset(rawResult, 0, sizeof(rawResult)); + + uint8_t apdu[] = {0x90, 0x78, id, 0x00, resultLen}; + int res = FIDOExchange(apdu, sizeof(apdu), + rawResult, sizeof(rawResult), + &result_len, &sw); + if (res) { + // error communicating + goto fail; + } + + if (sw != 0x9000) { + // card returned error + goto fail; + } + + // Copy into the actual result buffer + memcpy(result, rawResult, resultLen); + return true; + +fail: + memset(result, 0, resultLen); + return false; +} + diff --git a/client/ksx6924/ksx6924core.h b/client/ksx6924/ksx6924core.h new file mode 100644 index 000000000..35326c9b9 --- /dev/null +++ b/client/ksx6924/ksx6924core.h @@ -0,0 +1,95 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// 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. +//----------------------------------------------------------------------------- +// KS X 6924 (T-Money, Snapper+) protocol implementation +//----------------------------------------------------------------------------- + +#ifndef __KSX6924CORE_H__ +#define __KSX6924CORE_H__ + +#include +#include +#include "cmdhf14a.h" +#include "emv/emvcore.h" + +// Convenience structure for representing a date. Actual on-card format is in +// _ksx6924_internal_date_t. +struct ksx6924_date { + uint16_t year; + uint8_t month; + uint8_t day; +}; + +// Convenience structure for representing purse information. Actual on-card +// format is in _ksx6924_internal_purse_info_t. +struct ksx6924_purse_info { + uint8_t cardType; + uint8_t alg; + uint8_t vk; + uint8_t idCenter; + uint8_t csn[17]; // hex digits + null terminator + uint64_t idtr; + struct ksx6924_date issueDate; + struct ksx6924_date expiryDate; + uint8_t userCode; + uint8_t disRate; + uint32_t balMax; + uint16_t bra; + uint32_t mmax; + uint8_t tcode; + uint8_t ccode; + uint8_t rfu[8]; +}; + +// Get card type description +const char* KSX6924LookupCardType(uint8_t key, const char* defaultValue); + +// Get encryption algorithm description +const char* KSX6924LookupAlg(uint8_t key, const char* defaultValue); + +// Get IDCenter (issuer ID) description +const char* KSX6924LookupTMoneyIDCenter(uint8_t key, const char* defaultValue); + +// Get UserCode (ticket type) description +const char* KSX6924LookupTMoneyUserCode(uint8_t key, const char* defaultValue); + +// Get DisRate (discount type) description +const char* KSX6924LookupTMoneyDisRate(uint8_t key, const char* defaultValue); + +// Get TCode (telecom carrier ID) description +const char* KSX6924LookupTMoneyTCode(uint8_t key, const char* defaultValue); + +// Get CCode (credit card company ID) description +const char* KSX6924LookupTMoneyCCode(uint8_t key, const char* defaultValue); + +// Parses purse info in FCI tag b0 +bool KSX6924ParsePurseInfo( + const uint8_t *purseInfo, size_t purseLen, struct ksx6924_purse_info* ret); + +// Prints out a ksx6924_purse_info +void KSX6924PrintPurseInfo(const struct ksx6924_purse_info *purseInfo); + +// Selects the KS X 6924 application, returns all information +int KSX6924Select( + bool ActivateField, bool LeaveFieldON, + uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +// Selects the KS X 6924 application, returns true on success +bool KSX6924TrySelect(); + +// Gets the balance from a KS X 6924 card. Application must be already +// selected. +bool KSX6924GetBalance(uint32_t* result); + +// Proprietary get record command. Function unknown. +// result must be 10 bytes long. +bool KSX6924ProprietaryGetRecord( + uint8_t id, uint8_t* result, size_t resultLen); + +#endif /* __KSX6924CORE_H__ */ + diff --git a/client/obj/ksx6924/.dummy b/client/obj/ksx6924/.dummy new file mode 100644 index 000000000..e69de29bb